【转】深受开发者喜爱的10大Core Data工具和开源库

http://www.cocoachina.com/ios/20150902/13304.html

在iOS和OSX应用程序中存储和查询数据,Core Data是一个很好的选择。它不仅可以减少内存使用、提高性能,而且它可以使你避免写很多不必要的样板文件代码。

此外,Core Data API非常灵活,可以用在各种应用程序中,所有应用程序有不同的数据存数需求。

然而,这种灵活性意味着有时Core Data用起来可能稍微有点困难。即便你是一个Core Data专家,仍然会需要处理一些平常的任务,也会有很多犯错的可能性。

幸运的是,有很多很好的工具可以帮你解决问题,让Core Data更易于使用。为此我们选出了10个你应该知道和喜欢的工具和开源库。

注意:即便有这些优秀的工具和库,你仍需要很好地理解Core Data。如果你需要获得Core Data的更多经验,下载查阅我们的新手教程。

还要注意的是这篇文章以Objective-C为主,因为大部分Core Data库都是用Objective-C写的。如果你想学习怎样用Swift来使用Core Data,可下载查阅我们已经出版的书Core Data by Tutorials,这本书已经完全为iOS 8和Swift更新了!

#10. RestKit

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

下面的代码例子展示了如何设置RestKit来访问 OpenWeatherMap API,同时把/weather端点的JSON响应映射成为一个WFWeather 托管对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (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请求了Orlando(奥兰多)的天气,然后把响应指向上面提到的RKResponseDescriptor 实例。

4、最后,用需要的成功和失败的block(块)执行操作。当RestKit看到和定义的RKResponseDescriptor 想匹配的应答返回的时候,它将直接把数据映射到WFWeather 实例上。

以上代码不需要手动JSON解析、检查[NSNull null]、手动创建Core Data实体,或者连接一个API时任何其他必须做的事情。RestKit通过一个简单的映射字典把API响应转换成Core Data模型对象。没有比这个更容易的了。

要学习如何安装和使用RestKit,请下载查阅我们的 RestKit教程说明

#9. MMRecord

MMRecord是一个基于block(块)的集成库,它使用Core Data模型配置,自动创建并填充来自API响应的完整地对象图。当它在后台为你创建、获取、填充NSManagedObjects 实例时,使从web服务请求来的生成的本地对象尽可能简单。

它使得从web服务请求生成本地对象,像它在后台创建、获取、填充NSManagedObjects 实例那样简单。

下面的代码块,展示了怎样使用MMRecord来进行相同的Orlando天气调用,以及你在上面的RestKit例子中做的数据映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
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响应,你已经调用一个API,并且已用几行代码的响应数据填充了Core Data托管对象。

MMRecord 如何知道你在API响应中定位你的对象?你的托管对象必须是MMRecord 的子类,然后像下面所示那样重载keyPathForResponseObject :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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的响应对象的根相关的对象的位置。这个例子中,关键路径是data/2.5/weather 调用的main。

神奇之处不止这一点-MMRecord要求你创建一个服务器类,这个类知道如何请求你集成的API。谢天谢地,MMRecord附带一个基于AFNetworking 的服务器类的例子。

关于配置和使用MMRecord的信息,MMRecord Github repository的说明文件是最好的开始。

#8. Magical Record

灵感来源于Ruby on Rails' ActiveRecord 系统,提供了一系列支持一行实体的获取、嵌入和删除操作的类和类别。

下图是运行的MagicalRecord视图:

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

MagicalRecord让设置Core Data堆栈变得非常容易。而不是使用很多行的样板代码,你可以像下边这样只用AppDelegate文件里的一个方法调用来设置一个完整的Core Data堆栈。

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

在application:didFinishLaunchingWithOptions: 里,用你SQLite文件的名调用setupCoreDataStackWithStoreNamed。 这就建立了NSPersistentStoreCoordinator、NSManagedObjectModel 和NSManagedObjectContext 的实例,这样你就可以准备使用Core Data工作了。

进一步地了解如何安装和使用MagicalRecord,请参看我们的MagicalRecord 教程

#7. GDCoreDataConcurrencyDebugging

并发问题是在Core Data中调试最难的事情。performBlock API可以帮忙,但仍然很容易犯错。

你可以将开源项目GDCoreDataConcurrencyDebugging添加到自己的项目中,当在错误线程或者调度队列*问NSManagedObjects时,它通过控制台消息提醒你。

以下是在错误环境中访问NSManagedObject 实例的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__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 读取name, 这个对象最初在context1 被创建。

如果你使用GDCoreDataConcurrencyDebugging 运行上面的例子,你会看到下面的控制台消息,通知你有问题发生:

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

注意:在你的应用程序上传到App Store上去时,你得移除掉GDCoreDataConcurrencyDebugging ,它增加了少量的开销,那些开销无需存在于在发布的应用中。

在iOS 8和OS X Yosemite中,Core Data现在有检测并发问题的能力。想要启用这个新功能,你可以在启动时通过Xcode?s Scheme Editor 把-com.apple.CoreData.ConcurrencyDebug 1 传给你的应用程序。

然而,直到你逐步淘汰对OS更早版本的支持,GDCoreDataConcurrencyDebugging 都会在开发过程中一直通知你并发问题。

GDCoreDataConcurrencyDebugging README on Github 是安装和使用这个工具最好的资源信息。

#6. CoreData-hs

CoreData-hs 生成类别方法来执行Core Data模型里所有实体和属性的常见获取请求。创建这些方法不难,但它耗时--编码时节省每一点时间都是有价值的!

比如,你的天气应用程序里有一个查看天气预报和使用带有timeStamp,temp和 summary 属性的WFForecast 实体来模仿每天的预报的视图,CoreData-hs 将为你创建以下的类别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#import #import @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: 生成的实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
+ (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对象,你可以调用tempIsGreaterThan:inContext:sortDescriptors:error:,然后简单地传递目标温度,如下所示:

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

你将返回一个匹配对象的数据。

CoreData-hs 是一个轻量级的工具,如果你倾向于手动生成大量这种类型的请求,它可以节省你的时间。关于安装和使用说明,请查阅README on Github

#5. Core Data Editor

你可以在Core Data Editor的GUI中查看和编辑APP基于Core Data的数据模型,它支持XML、二进制和SQLite持久性存储类型。除了可以编辑基本属性,你还可以编辑和形象化数据关系。你也可以在使用Core Data Editor的同时使用Mogenerator工具 (第#2项中讨论)来创建模型代码。

Core Data Editor熟悉苹果的模式,展示不带Z前缀的数据,如果你曾看到Core Data生成的SQL文件,这个前缀你可能会熟悉。你可以在一个不错的表格格式里浏览应用程序数据库里的内容。它也支持预览二进制数据,比如图片,以及用一个标准日期选择器在线编辑数据。

【转】深受开发者喜爱的10大Core Data工具和开源库

如果你想创建一个种子文件或者只想导入数据,Core Data Editor可以生成一个CSV文件,把它转换成Core Data里持久化对象,如下所示:

【转】深受开发者喜爱的10大Core Data工具和开源库

安装Core Data Editor,从Thermal Core website下载免费试用版。解压下载的ZIP归档文件,把Core Data Editor.app 文件移动到你的Applications 目录里。这个应用的作者最近也把它开源了,如果你想了解它是如何工作的,以及提高自己,你可以去学习下。

第一次启动应用程序,它会引导你完成一个简短的设置过程。这个过程是可选的,但如果你至少指定iPhone模拟器目录和Xcode归档数据的目录,它将会加快速度。

注意:因为你得在GUI中选择导出的数据和模拟器目录,所以你可能在使用OS X Lion默认设置时会遇到问题,它会隐藏你的库文件夹。

在Mavericks OS X里,你可以通过在Finder的主目录里,选择View / Show View Options ,检查Show Library Folder来纠正这个问题。在Lion和Mountain Lion OS X中,同样的事情可以通过在终端输入chflags nohidden ~/Library/ 来完成。

更多有关Core Data的详情可以在这个网站Thermal Core's website上找到。

#4. SQLite3

有时当调试一个棘手的数据问题时,在底层的Core Data SQLite数据库中直接执行SQL查询是有帮助的。如果你没有扩展的数据库经验,这个可能不适合你。

使用SQLite3,首先打开Terminal终端,并导航到你应用程序的Documents 目录。根据你的安装情况,Documents目录类似于 ~/Library/Application Support/iPhone Simulator/7.1-64/Applications/{your app's ID}/Documents.

更改上面命名中7.1-64 来匹配你使用的模拟器的版本。{your app's ID} 由Xcode自动生成,并唯一地标识应用的安装。没有简单的办法找出哪个ID是你的。你可以在创建Core Data堆栈的时候添加日志记录,也可以寻找最近被多次修改的目录--这将是你当前工作的应用程序。

文档目录将包含一个扩展sqlite的文件,这个就是你应用程序的数据库文件。对于使用苹果Core Data模板的应用程序而言,文件名称要匹配你的应用的名字。用如下所示的SQLite3程序打开这个文件(这里的例子应用叫AddressBook,你的文件名字会有所不同):

1
$ sqlite3 AddressBook.sqlite

你将在控制台看到以下提示:

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

现在准备好对数据库执行标准的SQL查询。

比如,为了看到Core Data 使用的模式,可执行以下的命令:

1
sqlite> select * from sqlite_master;

SQLite用模式里的一个文本列表清单来响应你的查询,如下所示:

1
2
3
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底层使用SQLite的一部分。出于分析的目的,可以忽略它们。

注意:你不能直接写入SQLite Core Data数据库。苹果可以随时修改底层结构。

如果你真的有需要在生产应用程序中直接操作SQLite数据,你应该放弃Core Data,用原始的SQL访问。有几种流行的框架可以帮助你管理你应用中的SQL实现,包括FMDB和 FCModel

如果只是分析数据,到处访问SQLite数据库文件是没什么关系的--只要不修改它的内容。直接使用SQL分析数据的一个例子是不同属性进行分组和计算,以理解属性的差异。

举个例子,如果你有一个地址薄的应用例子,想知道在每个城市里有多少联系人生活,你可以用SQLite3提示执行以下命令:

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

SQLite会用地址薄数据库里每个不同城市的数据来作出响应,如下面的例子所示:

1
2
3
San Diego|23
Orlando|34
Houston|21

退出SQLite3终端程序,简单地执行以下的命令:

1
sqlite> .exit

想要了解更多SQLite3的信息,可打开终端查看man 页,并执行man sqlite3命令。

#3. MDMCoreData

MDMCoreData(免责声明--这个库是我的写的!)是一个开源类的集合,让使用Core Data变得更简单。它并不试图隐藏或抽象Core Data,而是执行最佳实践,以及减少所要的模板代码。这比Xcode Core Data模板是一个更好的选择。

MDMCoreData由以下四个类组成:

  • MDMPersistenceController,一个便利的控制器,支持创建多个child-managed object context 来建立一个高效的Core Data堆栈。它有一个内置的私有 managed object context,异步保存到SQLite仓库。

  • MDMFetchedResultsTableDataSource,获取ResultsController delegate和列表数据源。

  • MDMFetchedResultsCollectionDataSource,获取ResultsController delegate和集合数据源。

  • NSManagedObject+MDMCoreDataAdditions,一个托管对象类别,它提供辅助方法来消除模板代码,比如实体名。

MDMCoreData一个最大的特点是,它伴随着一个支持表数据源的Core Data--所以你不必担心要自己去实现。

你可以只设置MDMFetchedResultsTableDataSource实例的表数据源,而不是实现UITableViewDataSource 和NSFetchedResultsControllerDelegate 协议需要的所有方法。

当实例化MDMFetchedResultsTableDataSource 对象,你只要简单地在表视图里简单传送,并获取一个results controller:

1
2
3
4
5
6
7
8
9
- (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 有一个代理,有两个必须实现的方法。一个方法为你的表格配置cell:

1
2
3
4
5
- (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;
}

第二个方法处理删除操作:

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

实现两个所需的MDMFetchedResultsTableDataSource 方法,远比实现表数据源所有所需方法和获取results controller protocols要简单的多。

你可以在MDMCoreData Github repository 找到更多关于MDMCoreData的信息。

#2. Mogenerator

因为Core Data完全支持键/值编码(KVC)和键/值观察(KVO),就没必要实现自定义的NSManagedObject 类。当对你读写实体时,可以使用setValue:forKey: 和setValue:forKey: 。但这往往变得复杂和难于调试,因为字符串在编译的时候没法检查正确性。

比如,你有个person 的Core Data实体,你可以像这样读写属性:

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

上面的person 对象是一个属性名为firstName 的NSManagedObject 实例。想要读取firstName,你得用firstName 作为valueForKey:关键字。类似地,你可以使用setValue:forKey: 设置一个person 对象的first name。

更好的方法就是使用标准的访问方法或者点语法。然而,要这么做的话你必须为你的实体实现NSManagedObject一个自定义子类。你可以添加逻辑模型,比如获取请求和验证。

你可能使用Xcode的Create NSManagedObjectSubclass功能快速创建单个实体的子类。虽然是个捷径,但如果你的数据模型比较大,它会增加额外的开销,会在更改模型时带来许多问题。

重新创建子类意味着清除你所有的自定义模型逻辑--这就意味着你应该在自定义模型以外创建逻辑。它适合于创建带有托管对象属性和自定义模型逻辑类别的自定义子类的常规模式。

命令行工具Mogenerator 会自动化这些精确的任务。每个Core Data实体会生成两个类。第一个类是为机器消耗生成的,当模型改变的时候不断地被覆盖。第二个类是为你所有的自定义逻辑生成的,从来不被覆盖。

Mogenerator 有一系列其他好处,包括以下几点:

  • 读/写数值型属性时无需使用NSNumber 对象。

  • 处理设置的辅助方法

  • 创建新实体的辅助方法

  • 一个实体识别的方法

Mogenerator可以从Mogenerator website上可用的DMG文件来安装,或者通过Homebrew安装Mogenerator,打开终端,执行下面的命令:

1
brew install mogenerator

一旦安装,用cd 命令来改变你应用程序的目录,然后从终端运行Mogenerator,像这样:

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

在上面的命令中,你可以用-m 选项调用Mogenerator,紧随其后的是模型的位置。你也可以用-O 选项指定生成类的位置。当使用ARC时,你需要传递--template-var arc=true 选项。

你可以让Xcode通过创建Run Script Build Phase来运行Mogenerator。Build Phases是编译期间Xcode必须执行的任务的描述。

要添加一个Build Phase,首先选择target,选择Build Phases标签,然后选择菜单里的Editor / Add Build Phase / Add Run Script Build Phase 

在新Run Script下的Shell脚本文本区域里添加以下代码,确保修改mogenerator 的参数,来适配你的工程:

1
2
3
4
5
6
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运行Mogenerator。如果模型没什么改动的话,Mogenerator将什么都不做,然后退出。

为了快速生成子类,现在你已经把Mogenerator合并到你的工作流中,你应该好好利用它的其它特性。

比如,不用每次打开原始值,你只要给它们添加Value后缀, 如以下的代码片段所示:

1
2
3
4
5
6
7
8
9
// Without Mogenerator
if ([person.isFriend boolValue]) {
  // Do some work
}
  
// With Mogenerator
if (person.isFriendValue) {
  // Do some work
}

因为在Core Data里bool 类型是以NSNumber 类型存储的,在检查值是否为真之前你必须调用person 对象的boolValue 。通过Mogenerator,就不需要额外的步骤了,因为你只要简单地调用 isFriendValue即可。

如果Mogenerator对你的工具箱来说看起来像个有用的补充,你可以在Github repository 找到更多有关Mogenerator的信息。

#1. Instruments

Instruments 是OS X 和 iOS里为研究几乎所有性能和内存问题的重要工具--包括Core Data 问题。清单里的其它工具提供了许多自动化和便捷性,但Instruments通常会是研究任何问题或者性能调整的第一站。

下图是Time Profiler Core Data 模板,对Core Data配置来说是最有用的。

【转】深受开发者喜爱的10大Core Data工具和开源库

默认的Core Data 模板,添加了可选的Faults Instrument功能,通过提供以下功能来帮你调整和监控应用程序的性能:

  • Core Data Fetches Instrument,捕捉获取次数和获取操作的持续时间。

  • Core Data Cache Misses Instrument,捕捉导致缓存遗漏的故障事件。

  • Core Data Saves Instrument,捕捉托管对象上下文环境保存事件的信息。

  • Core Data Faults Instrument,捕捉NSManagedObjects 或相关关系的延迟初始化过程发生的故障事件的信息。

这个是Core Data应用程序中典型的instruments profile。你可以看到获取请求何时发生,以及请求所花时间,保存操作何时以及如何发生,以及何时出现故障灯。

【转】深受开发者喜爱的10大Core Data工具和开源库

要获取更多有关Instruments的信息,请下载我们的教程:How to Use Instruments in Xcode

下一步

Core Data是个很强大的框架,但它会有很多开发消耗,不过这篇文章中的工具和库提供了一些方法来高效、有效地帮你解决消耗问题。


作者:Matthew MoreyTwitter

Matthew Morey 是一位综合工程师、开发者、黑客以及创造者的多面手。作为iOS论坛里的一名活跃分子,以及MJD Interactive里的移动项目的主管,他已经领导了全球许多成功的移动项目。他是Buoy Explorer的创造者,专门适用于水上运动爱好者的一个海洋条件下的应用程序;以及Wrist Presenter,这个应用可以让你用智能手表控制无线演示。在不进行开发的时候,他喜欢旅行,滑雪和冲浪。他有关技术和商务的博客地址是 matthewmorey.com

上一篇:tidb 架构 ~Tidb学习系列(3)


下一篇:Django数据操作F和Q、model多对多操作、Django中间件、信号、读数据库里的数据实现分页