原文地址: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];
}
上面的代码做了如下事情:
-
首先,你创建一个RKEntityMapping对象,告诉RestKit如何映射API的响应结果到WFWeather 属性中。
-
这里RKResponseDescriptor绑定/data/2.5/weather接口的响应结果到RKEntityMapping实例上。
-
RKManagedObjectRequestOperation定义了执行哪个操作。在这个例子中,你从OpenWeatherMap API请求奥兰多的天气,把响应的结果指向上面提到的RKResponseDescriptor实例中。
-
最后,你执行这个请求。当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自带的工具:
下面是一个运行的截图: