文章目录
  1. 1. RestKit
  2. 2. MMRecord
  3. 3. Magical Record
  4. 4. GDCoreDataConcurrencyDebugging
  5. 5. CoreData-hs
  6. 6. Core Data Editor
  7. 7. SQLite3

Core Data 是ios和osx apps开发中对于数据持久化/数据查询不错的选择。不仅仅是它减少了内存的使用提高了性能,而且能够帮你减少冗余(boilerplate code 样板代码
)的代码。

除此之外,Core Data API 是极其灵活的,可用在很多(myriad)场景的app中,满足不透的储存需求。

但是,这种灵活也往往意味着有时候Core Data 使用起来有点困难,如果你是一个Core Data guru(专家/上师),其中还是有很多必须要做的通用配置,有很多地方也非常容易出错。
幸运的是,这里提供了狠很多的工具可以帮助你解决困难,让Core Data的使用相对简单些。下面的10工具是你必须了解的,想必你也会爱上她们。

注意: 即使这里有很多极好的工具和库,你仍然需要深入理解Core Data,如此方能从中受益。如果你需要更多的经验,狂点beginner tutorial keen ,可能需要翻墙(●’◡’●)。

还要注意的是这边文章主要针对于Objective-C,因为目前大多数的Core Data 大多数库都是Objective-C。如果你想学习怎么用swift使用Core Data ,可以关注Core Data by Tutorials系列教程,更新到了iOS8和Swift。

RestKit

RestKit 是一个基于Objective-C(后面用OC吧,字母好多)框架,用来和RESTful web services进行交互。RestKit为提供了Core Data一个映射引擎的实体来影射一个序列化对象(serialized response objects)到管理对象(managed objects)

下面是一些示例代码,用来教你如何建立ResKit 通过 OpenWeatherMap API去映射来自weather的JSON节点到WFWeather managed object;

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如何去WFWeather的属性.

  2. 在这里,RKResponseDescriptor 连接了/data/2.5/weather 到上面的RKEntityMapping实例。

  3. RKManagedObjectRequestOperation 定义了那个操作的执行。在上面的例子中,你先通过OpenWeatherMap API请求了Orlando的天气数据,点对点的相应上面的RKResponeseDescriptor实例。
  4. 最后,执行了一个带有success和failure blocks的操作。当RestKit 接收到返回的相应回去匹配一个被定义的RKResponseDescriptor实例,去映射数据到你之前的WFWeather对象。

在上面的代码中。不需要你手动解析JSON数据,也不需要你调整[NSNull null],不需要你创建Core Data实体,或者其他任何你之前连接到API时需要做的常规事项。RestKit通过一个简单的映射字典将API内置到Core Data数据模型对象中。没有比这更容易的了。

想要学习如何安装使用RestKit么,请猛戳Introduction to RestKit tutorial

MMRecord

MMRecord 是一个基于block基础配置的集合库,使用Core Data模型属性会根据API响应去自动创建和填充一个完整的对象图表。它会根据你的网络请求服务为你创建集成的本地对象,同样在背后还为你创建,取回,填充NSManagedObjectS 实例。

下面的代码将向你展示如何使用MMRecord去执行像RestKit一样的Orlando天气请求和数据映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

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并且写几行代码用你的responsedata填充你的Core Data managed objects。

MMRecord是如何在API response 去locate(定位)你的对象的呢?你的managed objects必须是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中指定这个对象相关联的更对象的位置。在这种情况下,the key path is main for the data/2.5/wweather call。(翻译不过来了)

MMRecord并不总是这么神奇的——MMrecord要求你创建一个服务器类去了解如何对你将集成的API发出请求。幸运的是,MMRecord和AFNetworking一样基于网络类。

想要获取完整的MMRecord创建和使用信息,请阅读MMRecord Github repository

Magical Record

效仿 Ruby on Rails’ ActiveRecord 系统,MagicalRecord 提供了一系列类和分类来统一实现对实体的取回插入删除的操作。

下面是MagicalRecord中的实现code

1
2
3
4
5
6
7
8
// Fetching
NSArray *people = [Person MR_findAll];

// Creating
Person *myPerson = [Person MR_createEntity];

// Deleting
[myPerson MR_deleteEntity];

MagicalRecord使得创建Core Data stack非常的容易。不需要跟多冗长的代码,你只需要在你的AppDelegate文件中调用一个方法就ok了。

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

return YES;
}

application:didFinishLaunchingWithOptions:中调用setupCoreDataStackWithStoreNamed 方法,当然还有你数据库的名字/。这样就会自动为CoreData创建 NSPersistentStoreCoordinator, NSManagedObjectModel and NSManagedObjectContext 实例。
具体使用请点击 MagicalRecord tutorial

GDCoreDataConcurrencyDebugging

目前,CoreData在使用中仍然有一些很难处理的问题。performBlock 的引入使得问题变得十分容易解决。
当你的NSManagedObjects对象 在线程或者队列上出现错误,那你可以将GDCoreDataConcurrencyDebugging 这个工程加入到你自己的工程上。
下面是来自context中的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创建于context1的对象中。
将GDCoreDataConcurrencyDebugging 加入你的工程运行,得到如下显示

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

注意:当你准备发布你的app到App Store上时,要移除掉GDCoreDataConcurrencyDebugging ,在发布版本里用不到它。

iOS 8 and OS X Yosemite 中的Core Data 已经可以解决目前的一些问题。你也可以通过Xcodeʼs Scheme Editor 设置-com.apple.CoreData.ConcurrencyDebug 1 来增加新功能。

但是,即使你(phase out)淘汰了之前的OS版本,还是建议你开发中继续使用GDCoreDataConcurrencyDebugging

详情查看GDCoreDataConcurrencyDebugging README on Github

CoreData-hs

CoreData-hs 主要集成了一些分类方法去执行取回模型的请求。创建这些方法并不难,但是太耗时间了,但是每一行代码都是有价值的。

举个例子,如果你想你的天气app有预测功能,用带有timeStamp,temp,summary的WFForecast类初始化实例,Core Data-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
66
67
#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:这个方法的实现

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;
}

一旦你集成这个方法就可以用它执行 特定的fetch requests 。再举个栗子,ifguo你需要取回温度超过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-h是一个轻量级工具,集成了很多的类型的手动请求,可以为你节省不少时间。

Core Data Editor

使用这款内置GUI的CoreData Editor,你可以看到并且编辑你的app 的CoreData-based。并且它还支持XML/二进制/SQLite这三种存储方式。除了这些基本操作外,你还可以编辑 展示你的数据关系。也可以使用其中的Mogenerator(discussed in item #2 below)来创建你的数据模型。

Core Data Editor 非常熟悉苹果的架构,如果你之前看过Core Data 的SQL文件,他并没有给你展现一个带有Z字母前缀的数据形式。你可以以一种表格的形式浏览你app中的数据库。同样,它还支持数据的预览,例如数据库中的图片,同样可以通过自己的date picker编辑数据库中内嵌的日期。

如果你需要创建一个子文件或者或者插入数,Core Data Editor 会提供一个CSV文件,把他变成你的持久化数据对象。

安装Core Data Editor,下载免费的使用点击Thermal Core website,解压下载好的ZIP文件,把Core Data Editor.app移到你的Applications中。最近app的作者把这个软件开源了,你可以去看看传送门
当你第一次安装这个app时,会有一个简短的安装指导。如果你指定一些选项,软件的安装速度会快点。

注意:因为你必须GUI中选择你导出的数据和模拟器路径。因为,OSX Lion的路径是隐藏的,可能在安装的时候会有点麻烦。在OSX苹果系统下,你可以通过到你的根目录选择View / Show View Options
选择Show Library Folder,隐藏文件就可以看到了。同样你也可以在终端输入以下代码

1
chflags nohidden ~/Library/

详细内容,请戳Thermal Core`s website

SQLite3

有时,在Core Data SQLite数据库中直接查询是非常方便的,尤其是在debug一个棘手的数据问题时。SQLite3是一个基于终端的从前到后的数据库,你必须有熟悉扩展数据库的经验,如果你没有,他可能并不适合你。
在使用SQLite3时,首先打开终端,来到你app的Document路径下。基于你之前的安装,Document的路径大概是这个样子的 ~/Library/Application Support/iPhone Simulator/7.1-64/Applications/{your app’s ID}/Documents

将是上面的7.1-64改成你模拟器的版本号。{your app`s ID}是xcode分配的独一无二的。没有什么简单的办法查找你的ID,或者你可以在创建数据库时打印你的ID。打开你的documents你会发现一个以sqlite结尾额文件,这就是你的数据库文件。对于使用临时core data的app来说,这个文件数据库名字是你的app名字。
如下打开你的数据库。

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 a ";"
sqlite>

好,现在准备查询数据库中的数据

1
sqlite> select * from sqlite_master;

打印如下:

1
2
3
4
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开头是表的横向分类,分析的目的,我们可以忽略这些。

注意:你不能直接向SQLite Core Data中写入数据。如果你需要直接操作数据库,你就把CoreData使用SQL的规则忘记吧,提供两个非常受欢迎的框架给你,FMDBFCModel
如果你只是分析数据,只要你不修改内容随便逛逛没什么事。

使用直接的SQL的一个例子来分析您的数据分组和计数截然不同的属性来查看属性的多样性

文章目录
  1. 1. RestKit
  2. 2. MMRecord
  3. 3. Magical Record
  4. 4. GDCoreDataConcurrencyDebugging
  5. 5. CoreData-hs
  6. 6. Core Data Editor
  7. 7. SQLite3