iOS CoreData从放弃到入门之transformable存储类型

已经有一段时间没有使用CoreData了,有些知识点都已忘记,尤其是一些比较重要但是使用频率又不是很高的知识点.比如CoreData的数据存储类型transformable.如果不了解这个存储类型,那么当app需要持久化一些复杂形式的数据内容时,你可能不太想用CoreData.本文将帮助你了解transformable,以及如何用它存储复杂形式的数据.

首先看一下CoreData拥有哪些数据存储类型
CoreData的数据存储类型:

coredata数据存储类型@2x.png

可以看到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实体,然后再添加好对应属性即可,如下图:

格式一实体@2x.png

接下来定义一个模型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];
}

看一下效果:

格式一添加记录.gif

至此格式一这样的数据已经持久化好了.

格式二数据持久化
(略).

格式三数据持久化
我们直接跳到对格式三这样的数据进行持久化.
可以看到格式三的数据中一些字段的值已经比较复杂了.比如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,如下:

格式三实体添加属性@2x.png

选择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,如下:

transformer使用@2x.png

设置完成后,改一下UI,运行一下程序:

格式三添加记录.gif

至此数据格式三的持久化基本完成.

总结

可以看到,使用transformable属性存储自定义对象时,我们只需要实现自定义对象的NSCoding协议,并告诉CoreData存储的时候调用它实现的NSCoding方法,为此我们新建了一个类FavoriteBookTransformer,这个类就是告诉CoreData存储这个属性时去调用它的归档方法,归档后你的自定义对象就以二进制的形式存储在数据库中,当从数据库中取出来时,同样FavoriteBookTransformer类会告知CoreData去调用它的解归档方法得到你的自定义对象.
理论上来说,只要这个对象实现了NSCoding协议,那么就可以将它存入CoreData中.比如UIColor对象,UIView对象.

后续

至此,你已经学会了如何使用transformable属性存储自定义对象了.那么相信你对CoreData的多线程读写处理也应该没问题了,可以作为思考题想想,呵呵!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,332评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,930评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,204评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,348评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,356评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,447评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,862评论 3 394
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,516评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,710评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,518评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,582评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,295评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,848评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,881评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,121评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,737评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,280评论 2 341

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,118评论 29 470
  • 前言 由于最近项目中在用Realm,所以把自己实践过程中的一些心得总结分享一下。 Realm是由Y Combina...
    一缕殇流化隐半边冰霜阅读 72,812评论 213 516
  • 1 前言 CoreData不仅仅是数据库,而是苹果封装的一个更高级的数据持久化框架,SQLite只是其提供的一种数...
    RichardJieChen阅读 2,972评论 2 2
  • OC的理解与特性OC作为一门面向对象的语言,自然具有面向对象的语言特性:封装、继承、多态。它既具有静态语言的特性(...
    LIANMING_LI阅读 511评论 0 0
  • 小姑娘: 今天你过来找我,还未开口,眼眶已红,一副极其委屈的模样。我放下手里的活儿,已经猜到你大概要说什么了。果不...
    珂雪的顾问笔记阅读 795评论 0 4