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自带的工具:

下面是一个运行的截图:

原文地址:https://www.cnblogs.com/YungMing/p/4346794.html