zoukankan      html  css  js  c++  java
  • 10个Core Data 工具和库

    原文地址:http://www.raywenderlich.com/75244/top-10-core-data-tools-libraries

    Core Data 是你在开发iOS和OSX应用时,用来持久化和查询数据的一个很好的选择。不仅仅因为它可以减少内存使用和提高性能,而且还可以让你免去编写很多不必要的样板代码的麻烦。

    此外,Core Data API非常灵活,使得这些代码可以在不同的app中,不同的数据存储需求中共享。

    然而,这种灵活性意味着有时Core Data 很难正常工作。即使你是一个Core Data大师,还是会犯错,需要你做大量普通的工作。

    幸运的是,有很多非常棒的工具可以帮助你脱离这些苦海,让Core Data 更容易工作。下面是我们前10个推荐的工具,希望你会喜欢!

    注意:即使有了这些强大的工具和库,您仍然需要对Core Data有很好的理解,才能理解它们的便利。如果你需要了解更多关于Core Data 的细节,可以查看我们的 beginner tutorial

    还要注意,由于目前大部分的Core Data库使用Objective-C语言编写,所以我们这篇文章也是使用Objective-C来讲解。如果你想了解怎么用Swift 语言来使用Core Data,可以查看我们即将发布的书:Core Data by Tutorials,这本书将全面升级为支持iOS8和Swift!

    10. RestKit

    RestKit是一个与RESTful web 服务交互的Objective-C框架。它提供了一个Core Data实体映射引擎,把序列化的响应对象直接映射到Core Data管理对象上。

    下面的代码演示了如果设置RestKit来访问 OpenWeatherMap API和映射 /weather api返回的JSON数据到管理对象上:

    - (void)loadForecastData {
         RKManagedObjectStore *store = self.managedObjectStore;
    
         // 1
         RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"WFWeather"
                                                 inManagedObjectStore:store];
        [mapping addAttributeMappingsFromArray:@[@"temp", @"pressure", @"humidity"]];
    
        // 2
        NSIndexSet *statusCodeSet = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
        RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor
                                              responseDescriptorWithMapping:mapping
                                              method:RKRequestMethodGET
                                              pathPattern:@"/data/2.5/weather"
                                              keyPath:@"main"
                                              statusCodes:statusCodeSet];
    
        // 3
        NSURL *url = [NSURL URLWithString:
               [NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/weather?q=Orlando"]];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc]
                                                initWithRequest:request
                                                responseDescriptors:@[responseDescriptor]];
        operation.managedObjectCache = store.managedObjectCache;
        operation.managedObjectContext = store.mainQueueManagedObjectContext;
    
        // 4
        [operation setCompletionBlockWithSuccess:
        ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
            NSLog(@"%@",mappingResult.array);
            [self.tableView reloadData];
        } failure:^(RKObjectRequestOperation *operation, NSError *error) {
            NSLog(@"ERROR: %@", [error localizedDescription]);
        }];
    
        [operation start];
    }
    

    上面的代码做了如下事情:

    1. 首先,你创建一个RKEntityMapping对象,告诉RestKit如何映射API的响应结果到WFWeather 属性中。

    2. 这里RKResponseDescriptor绑定/data/2.5/weather接口的响应结果到RKEntityMapping实例上。

    3. RKManagedObjectRequestOperation定义了执行哪个操作。在这个例子中,你从OpenWeatherMap API请求奥兰多的天气,把响应的结果指向上面提到的RKResponseDescriptor实例中。

    4. 最后,你执行这个请求。当RestKit发现一个返回的请求符合所定义的RKResponseDescriptor,它会直接映射数据到WFWeather

    在上面的代码中,不需要手动解析JSON、检查[NSNull null]、手动创建Core Data 实体或者其它连接一个API所需要做的事情。RestKit通过一个简单的映射字典,来把API响应转换到Core Data模型对象上,没有比这更简单的了。

    想要理解如何安装和使用RestKit,可以查看我们的Introduction to RestKit tutorial

    9.MMRecord

    MMRecord 是一个基于block的集成库。使用Core Data 模型配置来自动从API响应的数据中创建和生成完整的对象图。它会在后台帮我们创建,获取,生成NSManagedObjects实例。

    下面的代码演示了如何使用MMRecord来获取同样的奥兰多天气:

    NSManagedObjectContext *context =
       [[MMDataManager sharedDataManager] managedObjectContext];
    
    [WFWeather  startPagedRequestWithURN:@"data/2.5/weather?q=Orlando"
                      data:nil
                   context:context
                    domain:self
    resultBlock:^(NSArray *weather, ADNPageManager *pageManager, BOOL *requestNextPage) {
        NSLog(@"Weather: %@", weather);
    }
    failureBlock:^(NSError *error) {
        NSLog(@"%@", [error localizedDescription]);
    }];
    

    只需几行代码,不需要复杂的网络请求代码,自动解析JSON数据,就可以生成你的Core Data 管理对象。

    那MMRecord是如何在API响应中定位你的对象呢?你的管理对象必须是MMRecord的子类,同时重写了keyPathForResponseObject方法:

    @interface WFWeather : MMRecord
    @property (nonatomic) float temp;
    @property (nonatomic) float pressure;
    @property (nonatomic) float humidity;
    @end
    
    @implementation WFWeather
    @dynamic temp;
    @dynamic pressure;
    @dynamic humidity;
    
    + (NSString *)keyPathForResponseObject {
        return @"main";
    }
    
    @end
    

    keyPathForResponseObject返回一个键值路径来指定这个对象相对于API返回的JSON数据结构的根对象的位置。这种情况下, main 键值路径对应于 data/2.5/weather api调用。

    MMRecord还需要你创建一个 server类,用来进行API请求的,幸运的是,MMRecord提供了一个基于AFNetworking server类的例子。

    想要了解更多关于如何安装和使用MMRecord的信息,可以查看它的github仓库 MMRecord Github repository

    8.Magical Record

    MagicalRecord提供了一组类和categories,使用一行代码即可实现实体读取,插入和删除操作:

    // Fetching
    NSArray *people = [Person MR_findAll];
    
    // Creating
    Person *myPerson = [Person MR_createEntity];
    
    // Deleting
    [myPerson MR_deleteEntity];
    

    MagicalRecord提供了很方便的方法来启动一个Core Data 栈,只需要在AppDelegate文件中,加入如下代码:

    - (BOOL)application:(UIApplication *)application 
                 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // 1
        [MagicalRecord setupCoreDataStackWithStoreNamed:@"ExampleDatabase.sqlite"];
    
        return YES;
    }
    

    它会帮你创建NSPersistentStoreCoordinator , NSManagedObjectModel 和 NSManagedObjectContext实例。

    7. GDCoreDataConcurrencyDebugging

    并发性问题在Core Data中是很难调试的。尽管 perfomBlock APIs有提供一些帮助,但是它很容易发生错误。

    这个开源项目 GDCoreDataConcurrencyDebugging 可以添加到你的工程中,当NSManagedObjects关联到错误的线程中或者派遣到错误的队列中时,它通过控制台信息来提醒你。

    下面是在一个错误的上下文中访问NSManagedObject实例:

    __block NSManagedObject *objectInContext1 = nil;
    
    [context1 performBlockAndWait:^{
    
        objectInContext1 = [[NSManagedObject alloc] initWithEntity:entity  
                                    insertIntoManagedObjectContext:context1];
        objectInContext1.name = @"test";
    
        NSError *saveError;
        if ([context1 save:&saveError] == NO) {
    
            NSLog(@"Error: %@", [saveError localizedDescription]);
        }
    }];
    
    
    // Invalid access
    [context2 performBlockAndWait:^{
        NSString *name = objectInContext1.name;
    }];
    

    上面的代码中,你视图在上下文context2中,访问在context1中创建的对象。

    如果你运行上面的代码(添加了GDCoreDataConcurrencyDebugging项目),你将在控制台看到如下信息:

    2014-06-17 13:20:24.530 SampleApp[24222:60b] CoreData concurrency failure
    

    注意,当你要发布到App Store时,记得移除这个GDCoreDataConcurrencyDebugging项目。

    在iOS8和 OS X Yosemite 系统下,Core Data已经支持检测并发性问题。在Xcode的 Scheme编辑器中,传入 -com.apple.CoreData.ConcurrencyDebug 为 1来在你的应用启动时开启。

    更多的信息可以查看它的github仓库GDCoreDataConcurrencyDebugging

    6.CoreData-hs

    CoreData-hs生成了一些通用的读取请求的category 方法。这些方法都不是很复杂,但是需要你很多时间,这些category方法可以为你节省很多时间!

    假如,你有一个天气预报的视图,每一天的天气预报模型封装为一个WFForecast实体(有timeStamp, temp和 summary属性)。CoreData-hs会为你创建如下的category:

    #import <CoreData/CoreData.h>
    #import <Foundation/Foundation.h>
    @interface WFForecast (Fetcher)
    
    + (NSArray *)summaryIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)timeStampIsEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)timeStampIsLessThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)timeStampIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)timeStampIsGreaterThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)timeStampIsLessThanOrEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)timeStampIsNotEqualTo:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)timeStampIsBetwixt:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryIsLike:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryContains:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryMatches:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryBeginsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)summaryEndsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempIsLike:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempContains:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempMatches:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempBeginsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    + (NSArray *)tempEndsWith:(id)object inContext:(NSManagedObjectContext *)context sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock;
    
    @end
    

    如你所见,生成了很多方法。下面是tempIsGreaterThan:inContext:sortDescriptors: error: 方法的实现:

    + (NSArray *)tempIsGreaterThan:(id)object inContext:(NSManagedObjectContext *)context 
                   sortDescriptors:(NSArray *)sort error:(void(^)(NSError *error))errorBlock {
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"WFForecast"];
        [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"temp > %@", object]];
        [fetchRequest setSortDescriptors:sort];
        NSError *err = nil;
        NSArray *results = [context executeFetchRequest:fetchRequest error:&err];
        if(!results && errorBlock) {
            errorBlock(err);
            return nil;
        }
        return results;
    }
    

    现在你可以使用这个方法来执行获取请求。例如,你想查询出所有温度大于 70度的WFForecast对象,你可以:

    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"temp" 
                                                                     ascending:YES];
    NSArray *results = [WFForecast tempIsGreaterThan:@(70)
                                          inContext:self.managedObjectContext
                                 sortDescriptors:@[sortDescriptor]
                                           error:^(NSError *error) {
    
        NSLog(@"Error: %@", [error localizedDescription]);
    }];
    

    5. Core Data Editor

    Core Data Editor 提供了可视化的界面来让你查看和编辑你应用中的基于Core Data模型的数据,支持XML,二进制和SQLite类型。除了可以修改基本的属性,你还可以编辑和可视化地查看数据的关系。

    如果你需要查找一个文件或者导入数据,Core Data Editor支持 导入CSV类型文件,然后转化成持久化对象:

    你可以从Thermal Core website下载免费试用版,如果你想知道它是如何工作的,这个app的作者前不久开源了它的代码

    更多的细节可以查看Thermal Core’s 网站

    4.SQLite3

    当有时候需要调试一些数据问题时,直接在Core Data SQLite数据库中执行SQL查询可能会更方便。SQLite3是一个基于命令行的嵌入式数据库,默认在所有的Mac中都安装了。

    为了使用SQLite3,首先打开终端,导航到你的app的Documents目录:~/Library/Application Support/iPhone Simulator/7.1-64/Applications/{your app's ID}/Documents.

    在这个Documents目录下,有一个sqlite后缀的文件,这个就是你的app的数据库文件。命令行,打开这个文件:

    $ sqlite3 AddressBook.sqlite
    

    你将看到下面的信息:

    SQLite version 3.7.13 2012-07-17 17:46:21
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite>
    

    现在你可以执行标准的SQL查询了,例如,为了查询Core Data 正在使用的 schema数据,可以:

    sqlite> select * from sqlite_master;
    

    SQLite将响应你的请求:

    table|ZMDMPERSON|ZMDMPERSON|3|CREATE TABLE ZMDMPERSON ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZISNEW INTEGER, ZFIRSTNAME VARCHAR )
    table|Z_PRIMARYKEY|Z_PRIMARYKEY|4|CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER)
    table|Z_METADATA|Z_METADATA|5|CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB)
    sqlite>
    

    所有以Z开头的表列,都是Core Data使用的,我们可以忽略它们。

    注意,你不应该直接修改这个文件的内容。

    假如你有一个联系人地址本app,你想知道每个城市的联系人数量,可以执行下面的查询:

    SELECT t0.ZCITY, COUNT( t0.ZCITY ) FROM ZMDMPERSON t0 GROUP BY t0.ZCITY
    

    返回如下结果:

    San Diego|23
    Orlando|34
    Houston|21
    

    如果想退出当前的SQLite3终端程序:

    sqlite> .exit
    

    3.MDMCoreData

    这个库是本文作者写的。

    它包括下面四个类:

    • MDMPersistenceController - 一个控制器,设置一个高效的Core Data栈,支持创建多个子管理对象上下文。它还有一个内建的私有管理对象上下文,提供异步地写入SQLite存储。

    • MDMFetchedResultsTableDataSource - 实现了fetched results controller delegate 和 一个 table data source.

    • MDMFetchedResultsCollectionDataSource - 实现了fetched results controller delegate 和 一个collection data source.

    • NSManagedObject+MDMCoreDataAdditions - 一个管理对象的类别,提供辅助方法来消除样板代码,如​​实体名称。

    MDMCoreData的一个很大的特点是,它配备了一个Core data备份表中的数据源 - 所以你不必担心你自己实现一个。

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.tableDataSource = [[MDMFetchedResultsTableDataSource alloc] 
                   initWithTableView:self.tableView 
            fetchedResultsController:[self fetchedResultsController]];
        self.tableDataSource.delegate = self;
        self.tableDataSource.reuseIdentifier = @"WeatherForecastCell";
        self.tableView.dataSource = self.tableDataSource;
    }
    

    一个 MDMFetchedResultsTableDataSource 有一个委托,需要实现两个方法,一个方法用来配置你的tableview cell:

    - (void)dataSource:(MDMFetchedResultsTableDataSource *)dataSource
               configureCell:(id)cell
                  withObject:(id)object {
    
        OWMForecast *forecast = object;
    
        UITableViewCell *tableCell = (UITableViewCell *)cell;
        tableCell.textLabel.text = forecast.summary;
        tableCell.detailTextLabel.text = forecast.date;
    }   
    

    第二个方法管理删除:

    - (void)dataSource:(MDMFetchedResultsTableDataSource *)dataSource
              deleteObject:(id)object
               atIndexPath:(NSIndexPath *)indexPath {
    
        [self.persistenceController.managedObjectContext deleteObject:object];
    }
    

    想要了解更多信息,可以访问它的github 仓库 MDMCoreData Github repository

    2. Mogenerator

    Core Data 支持KVC和KVO,当你访问和修改你的实体时,可以通过setValue:forKey:valueForKey:方法实现。但是这些方法无法在断点调试中查询相关的信息。

    例如,你有一个person 的Core Data 实体,你可以像下面这样读取和修改它的属性:

    NSString *personName = [person valueForKey:@"firstName"];
    [person setValue:@"Ned" forKey:@"firstName"];
    

    这个person是一个NSManagedObject的实例,有一个firstName属性。

    一个更好的方法是使用点操作符或者传统的属性访问方法,但是,如果你这么做,你必须为你的实体实现自定义的NSManagedObject子类,这让你添加模型的逻辑,例如获取请求和验证数据。

    为了把我们自定义的逻辑代码分离出这个NSManagedObject子类中,我们还需要重新创建一个子类。命令行工具 Mogenerator 就是自动帮我们完成这个任务的。它会为每个Core Data 实体生成两个类。第一个类会在模型改变时不断地被重写,第二个类是你自己的逻辑,不会被重写。

    Mogenerator 可以在 Mogenerator website下载DMG格式文件来安装,也可以通过Homebrew来安装:

    brew install mogenerator
    

    安装好后,在命令行下定位到你的app所在的目录,然后在终端中运行Mogenerator:

    $ mogenerator -m MySampleApp/ExampleModel.xcdatamodeld -O MySampleApp/Model --template-var arc=true
    

    当使用arc时,后面需要输入 --template-var arc=true.

    你也可让Xcode自动为你运行Mogenerator。通过创建一个Run Script Build Phase。为了添加一个Build Phase,首先选择target,选择Build Phases ,然后选择Editor / Add Build Phase / Add Run Script Build Phase。

    添加如下的shell 脚本(记得修改成你项目的文件名):

    if [ "${CONFIGURATION}" == "Debug" ]; then
    echo "Running Mogenerator"
    mogenerator -m MySampleApp/ExampleModel.xcdatamodeld -O MySampleApp/Model --template-var arc=true
    echo "Finished Mogenerator"
    else
    echo "Skipping Mogenerator"
    fi
    

    上面的脚本,会让Xcode每次运行debug构建时,自动运行Mogenerator,如果你的模型没有改变,它不会执行任何操作。

    下面是替换之前的操作:

    // Without Mogenerator
    if ([person.isFriend boolValue]) {
        // Do some work
    }
    
    // With Mogenerator
    if (person.isFriendValue) {
        // Do some work
    }
    

    想要更多信息,可以查看Mogenerator的github仓库

    1. Instruments

    Xcode自带的工具:

    下面是一个运行的截图:

  • 相关阅读:
    怎样改动、扩展并重写Magento代码
    解决Gradle minifyEnabled无法找到错误
    使用Hadoop的MapReduce与HDFS处理数据
    cmake 学习笔记(一)
    简单的日志系统
    WebStorm 7.0 注冊码
    DOS命令大全--具体解释
    SQL SERVER之数据查询
    闰年的定义
    Javascript作用域链
  • 原文地址:https://www.cnblogs.com/YungMing/p/4346794.html
Copyright © 2011-2022 走看看