已经有一段时间没有使用CoreData了,有些知识点都已忘记,尤其是一些比较重要但是使用频率又不是很高的知识点.比如CoreData的数据存储类型transformable.如果不了解这个存储类型,那么当app需要持久化一些复杂形式的数据内容时,你可能不太想用CoreData.本文将帮助你了解transformable,以及如何用它存储复杂形式的数据.
首先看一下CoreData拥有哪些数据存储类型
CoreData的数据存储类型:
可以看到CoreData的数据存储类型包括整型,浮点型,字符串型,布尔值,日期,二进制数据,最后一个就是要讲的transformable类型.transformable翻译过来就是可变的,即它代表的就是一种可变类型.因此可以用它来存储数组类型,字典类型,甚至自定义对象类型.
例子
接下来将通过一个保存人的信息的例子说明如何使用它.美好的事情即将发生...
就像课堂上数学老师讲课时的例子,通常我们学习保存的数据,格式是这样的:
{
"body": {
"data": {
"name": "小明",
"age": "24",
"height": "172",
"weight": "52"
},
"dataVersion": "V1.0"
},
"statusCode": "1",
"statusInfo": "成功"
}
然而课后作业是这样的:
{
"body": {
"data": {
"name": "小明",
"age": "24",
"height": "172",
"weight": "52",
"habits": [
"reading",
"running",
"singing"
]
},
"dataVersion": "V2.0"
},
"statusCode": "1",
"statusInfo": "成功"
}
这样的:
{
"body": {
"data": {
"name": "小明",
"age": "24",
"height": "172",
"weight": "52",
"habits": [
"reading",
"running",
"singing"
],
"favoriteBooks": [
{
"bookId": "3213",
"name": "一只特立独行的猪",
"publisher": "延边出版社"
},
{
"bookId": "5345",
"name": "思维里的墙",
"publisher": "工业出版社"
},
{
"bookId": "6546",
"name": "HTTP权威指南",
"publisher": "未知出版社"
}
]
},
"dataVersion": "V3.0"
},
"statusCode": "1",
"statusInfo": "成功"
}
别着急,下面将一步步对这些格式的数据进行存储.
格式一数据持久化
对于格式一这样的数据还是很简单的,只需要新建一个People实体,然后再添加好对应属性即可,如下图:
接下来定义一个模型People:
@interface People : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *height;
@property (nonatomic, retain) NSString *weight;
@property (nonatomic, retain) NSString *age;
@end
在viewcontroller里面实现一个添加人员记录的方法:
- (void)addPeople:(id)sender
{
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
NSEntityDescription *entityDes = [NSEntityDescription entityForName:@"People" inManagedObjectContext:appDelegate.managedObjectContext];
People *people = [[People alloc] initWithEntity:entityDes insertIntoManagedObjectContext:appDelegate.managedObjectContext];
people.name = [NSString randomStringWithLength:6 type:RandomStringTypeAlphabet];
NSInteger age = 20+arc4random()%20;
people.age = [NSString stringWithFormat:@"%ld", age];
NSInteger height = 150+arc4random()%40;
people.height = [NSString stringWithFormat:@"%ld", height];
NSInteger weight = 40+arc4random()%40;
people.weight = [NSString stringWithFormat:@"%ld", weight];
[appDelegate saveContext];
[self.peoples addObject:people];
[self.tableView reloadData];
}
看一下效果:
至此格式一这样的数据已经持久化好了.
格式二数据持久化
(略).
格式三数据持久化
我们直接跳到对格式三这样的数据进行持久化.
可以看到格式三的数据中一些字段的值已经比较复杂了.比如favoriteBooks字段:
"favoriteBooks": [
{
"bookId": "3213",
"name": "一只特立独行的猪",
"publisher": "延边出版社"
},
{
"bookId": "5345",
"name": "拆掉思维里的墙",
"publisher": "工业出版社"
},
{
"bookId": "6546",
"name": "HTTP权威指南",
"publisher": "未知出版社"
}
]
favoriteBooks字段的值是一个数组,数组的元素是另一个模型.分析到这里就可以编写代码了.先对People.h文件动一下刀.
于是People.h文件变为:
@interface People : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *height;
@property (nonatomic, retain) NSString *weight;
@property (nonatomic, retain) NSString *age;
@property (nonatomic, retain) NSArray *favoriteBooks;
@end
@interface favoriteBook : NSObject
@property (nonatomic, copy) NSString *bookId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *publisher;
@end
接下来是重点了,对CoreData的People实体添加属性favoriteBooks,如下:
选择favoriteBooks的类型为Transformable类型.
做完这一步,运行一下程序是不是一启动就崩溃了?崩溃就对了,因为我们对实体添加了属性,但是底层的数据库却还是以前的字段,导致不兼容.
Xcode控制台提示:
Unresolved error Error Domain=YOUR_ERROR_DOMAIN Code=9999 "Failed to initialize the application's saved data" UserInfo={NSLocalizedFailureReason=There was an error creating or loading the application's saved data., NSLocalizedDescription=Failed to initialize the application's saved data, NSUnderlyingError=0x600000058840 {Error Domain=NSCocoaErrorDomain Code=134100 "(null)" UserInfo={metadata={
NSPersistenceFrameworkVersion = 752;
NSStoreModelVersionHashes = {
People = <6261e331 17cd5ff4 214bfa07 fd238e1f 9b0b0f3e 48725d9a 3b3452b3 477d010e>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "F00E8A66-2637-42D9-96CB-42591B105BAE";
"_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store}}}, {
NSLocalizedDescription = "Failed to initialize the application's saved data";
NSLocalizedFailureReason = "There was an error creating or loading the application's saved data.";
NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=134100 \"(null)\" UserInfo={metadata={\n NSPersistenceFrameworkVersion = 752;\n NSStoreModelVersionHashes = {\n People = <6261e331 17cd5ff4 214bfa07 fd238e1f 9b0b0f3e 48725d9a 3b3452b3 477d010e>;\n };\n NSStoreModelVersionHashesVersion = 3;\n NSStoreModelVersionIdentifiers = (\n \"\"\n );\n NSStoreType = SQLite;\n NSStoreUUID = \"F00E8A66-2637-42D9-96CB-42591B105BAE\";\n \"_NSAutoVacuumLevel\" = 2;\n}, reason=The model used to open the store is incompatible with the one used to create the store}";
}
看着这么长,就跟考试时数学里的应用题一样.其实也就是吓吓你.我们只要看最后一句就可以了,
"reason=The model used to open the store is incompatible with the one used to create the store",提示我们模型不兼容.因此我们需要进行版本迁移.
CoreData版本迁移
一般的版本迁移像更改属性的类型,删除了某个属性,添加了某个属性等.这样的版本迁移处理起来其实还是比较简单的.只需要在创建持久化仓库的时候,增加一些配置就可以了.重点.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
字典options就是配置,是CoreData为我们准备的轻量级迁移.
将NSMigratePersistentStoresAutomaticallyOption,自动迁移持久化仓库设置为YES
NSInferMappingModelAutomaticallyOption,自动映射模型设置为YES.即可.
再次运行下程序,是不是就OK了.
关于更复杂情况的迁移(也就是你的CoreData实体已经改的面目全非,惨不忍睹,所有在场的工作人员都哭了的那种),需要另开一篇,这里写不下了.
让我们再回到原来的话题上,继续持久化数据格式三.
新建一个转换类FavoriteBookTransformer
@interface FavoriteBookTransformer : NSValueTransformer
@end
@implementation FavoriteBookTransformer
+ (Class)transformedValueClass
{
return [NSArray class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (id)transformedValue:(id)value
{
return [NSKeyedArchiver archivedDataWithRootObject:value];
}
- (id)reverseTransformedValue:(id)value
{
return [NSKeyedUnarchiver unarchiveObjectWithData:value];
}
@end
给之前建的FavoriteBook类实现NSCoding协议:
@implementation FavoriteBook
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.bookId forKey:@"bookId"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.publisher forKey:@"publisher"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [[self class] new];
if (self)
{
self.bookId = [aDecoder decodeObjectForKey:@"bookId"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.publisher = [aDecoder decodeObjectForKey:@"publisher"];
}
return self;
}
@end
然后在CoreData里选中favoriteBooks属性并设置它的value Transform为刚才的类FavoriteBookTransformer,如下:
设置完成后,改一下UI,运行一下程序:
至此数据格式三的持久化基本完成.
总结
可以看到,使用transformable属性存储自定义对象时,我们只需要实现自定义对象的NSCoding协议,并告诉CoreData存储的时候调用它实现的NSCoding方法,为此我们新建了一个类FavoriteBookTransformer,这个类就是告诉CoreData存储这个属性时去调用它的归档方法,归档后你的自定义对象就以二进制的形式存储在数据库中,当从数据库中取出来时,同样FavoriteBookTransformer类会告知CoreData去调用它的解归档方法得到你的自定义对象.
理论上来说,只要这个对象实现了NSCoding协议,那么就可以将它存入CoreData中.比如UIColor对象,UIView对象.
后续
至此,你已经学会了如何使用transformable属性存储自定义对象了.那么相信你对CoreData的多线程读写处理也应该没问题了,可以作为思考题想想,呵呵!