Core Data: 非标准数据类型总结

陈旧的 Core Data 文档终于在 iOS 9 正式发布的当天更新了,文档更新历史上写着此次更新重写了文档,是关于当前 API 实践的重大更新。然而本文的部分被误删了是吧,就剩下下面一段话了。原本关于这部分的内容就我一直很困惑,如今倒好文档也给删了......

新文档

不过,从 google 的缓存还可以看以前的文档,选择「Text-only version」模式,阅读上会舒服一些。
Core Data 支持的数据类型

Core Data 支持的标准数据类型包括:
1.不同类型的数字,包括16、32、64位的 Int,两种浮点数,Decimal,布尔值,这些数据都会被封装成 NSNumber,在生成子类的时候可以选择是否使用原始的数据类型;
2.String, 使用 Unicode 存储,由于 Unicode 的复杂性,对 String 类型的属性进行排序和搜索会比较复杂;
3.Date,仅仅是对 NSTimeInterval (也就是 Double)的封装,不包含时区信息,你需要自行保存时区的信息,默认在子类中使用 NSDate 类型,可以在生成子类时使用原始数据类型,会使用 NSTimeInterval;
4.BinaryData,也就是 NSData 类型。
以上这些标准类型都是不可变的类型,剩下的两种:Transformable 和 Undefined 用于支持非标准类型的数据。对于 Undefined 类型,记得在右侧的 Data Model Inspector 里勾选 Transient 选项。支持非标准类型时,你最好也使用不可变类型,不然 Core Data 无法跟踪数据的变化。也就是说每次给属性设置一个新的值,Core Data 才知道你的 NSManagedObject 发生了变化,这点在你使用自定义数据类型时很重要。

那不是以上这些类型的对象和结构体怎么办?总体来说,支持非标准类型有两条路:Transformable Attribute 和 Transient Attribute。

Transformable Attribute

使用这种属性时工作量很小,适合遵守 NSCoding 协议的类型,在子类中将该属性类型更改为你需要的类型即可,剩下的工作由 Core Data 替我们完成。缺点是这种属性的内部存储由于是使用属性列表 property list 这种格式,所以比较浪费空间,在性能上也不太好。如果你对性能和空间没有很特别的要求,使用这种就可以了。


Transformable Attribute

如果你想更加高效地存储,你可以提供自定义的 value transformer 来实现数据的转化。(我是不会这个的)

Undefined(Transient Attribute)

Transient Attribute
Backup Attribute

对于 transient attribute,必须指定 Attribute Type 为 Undefined,而且要选中「Transient」。「Allows External Storage」不是必选选项,选中该项后由 Core Data 决定数据是否存放在其他地方;@NSManaged 修饰符表示由 Core Data 来负责动态生成存取方法,对于 transient attribute,无法使用 @NSManaged 修饰符,因为这是需要开发者负责的。

Transient Attribute 类似 Swift 里的计算属性,需要根据其他属性来生成。Core Data 在 fetch 时并不会处理这种属性,不会存储这种属性,也不会跟踪这种属性的变化。总之就是对这种属性不管不问,完全由开发者自己负责。不过,处理这种属性的全过程都可以自定义,应对各种需求。因为 Core Data 对这种属性完全是放任自流的,这种方式有额外的工作:需要我们实现属性的存取方法 accessor methods。比如,处理 UIImage 对象,需要先用一个备份属性使用 Core Data 支持的类型,然后 transient attribute 利用前者生成我们需要的 UIImage 对象。

使用 transient attribute,必须要提到 primitive property,这是 Core Data 对属性的内部实现。可以通过 primitiveValueForKey(key)setPrimitiveValue(newValue, forKey: )两个方法来获得对 primitive property 的访问。

@NSManaged private var imageData: NSData?
var image: UIImage?{
    get {
        let ImageKey: String = "image"
        willAccessValueForKey(ImageKey)
        var imageObject = primitiveValueForKey(ImageKey) as? UIImage
        didAccessValueForKey(ImageKey)
        if imageObject == nil{
            imageObject = UIImage(data: imageData!)
        }
        
        return imageObject
    }
    
    set {
        let ImageKey: String = "image"
        let ImageDataKey = "imageData"
        willChangeValueForKey(ImageKey)
        self.setPrimitiveValue(newValue, forKey: ImageKey)
        didChangeValueForKey(ImageKey)
        let imageRawData = UIImageJPEGRepresentation(newValue!, 1.0)!
        //更新persistent attribute 时使用 KVC 方法,这样 Core Data 才知道该属性变化了。
        self.setValue(imageRawData, forKey: ImageDataKey) 
    }
}

由于Core Data 基本上对 transient attribute 不管不问,同时不支持的数据类型也通常会有访问代价大的问题。因此,可以基于不同的优化策略对 transient attribute 进行定制。
获取策略有两种:
1.按需获取 The On-demand Get
上面的代码就是。
2.预获取 The Pre-calculated Get
在对象被 fetch 时获取对象

override func awakeFromFetch() {
    super.awakeFromFetch()
    if let imageRawData = self.imageData{
        let imageKey = "image"
        let imageObject = UIImage(data: imageRawData)
        self.setPrimitiveValue(imageObject, forKey: imageKey)
    }
}

更新策略有两种:
1.即时更新 The Immediate-Update Set
上面的示例里就是
2.延迟更新 The Delayed-Update Set
在对象被保存时才更新

override func willSave() {
    super.willSave()
    let imageKey = "image"
    let imageDataKey = "imageData"
    //这里不必使用 KVC 方法
    if  let image = self.primitiveValueForKey(imageKey) as? UIImage {
        let imageRawData = UIImageJPEGRepresentation(image, 1.0)
        self.setPrimitiveValue(imageRawData, forKey: imageDataKey)
    }else{
        self.setPrimitiveValue(nil, forKey: imageDataKey)
    }
}

同时还需要更改 transient attribute 的 set 方法,不要在 set 里更新用作备份的属性。

    set {
        let ImageKey: String = "image"
        willChangeValueForKey(ImageKey)
        self.setPrimitiveValue(newValue, forKey: ImageKey)
        didChangeValueForKey(ImageKey)
    }

Binary Large Data Objects (BLOBs)

在上一节中,使用了 NSData 作为备份属性来存储图像的二进制数据,这里也可以不用 transient attribute 这种方式,直接用 NSData 类型存储,那么有几个问题需要考虑。

首先是内存占用,图像一般比其他数据大得多。下面的 persistent store 类型中,面对会占用大量内存的数据,SQLite store 是最佳选择。

Persistent Store Type

其次,选择更合理的访问策略。与其将 BLOBs 对象直接作为属性,不如将其作为其他NSManagedObject的属性并将两者使用关系(relationship)联系起来。因为访问关系默认是 faults 状态,只有访问属性时才会填充数据,这样可以最大限度地降低内存占用。

另外,对于这种 BLOBs,可以选择在文件系统中存储,而在 Core Data 里只需要维持该资源的 URL,在需要的时候才获取该资源。

总结

对于不支持的类型,基本策略是转化为被支持的类型。
大部分对象类型都遵守 NSCoding 协议,可以使用 Transformable Attribute,这是最简单的方法,可能会有一些性能上的问题,需要优化或是同时做其他事情的话就使用 Transient Attribute。
对不支持 NSCoding 协议的类型,比如 CGRect,CGSize 这些结构体,可以转化为遵守 NSCoding 协议的 NSValue 对象,这样可以使用 Transformable Attribute 来保存了。又或者将类型的成员数据拆分,比如 CGSize 有 width 和 height 两个都是 CGFloat 成员变量,可以将两个成员变量转换为 float 类型,这样就可以用 transient attribute 来获取对应的 CGSize 数据了。
这部分内容不适合 relationship。另外,如果你自定义某个类作为NSManagedObject子类的属性,直接这样做是不合适的,你可以考虑将这个类用NSManagedObject的子类来实现并用 relationship 来连接前者。

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

推荐阅读更多精彩内容

  • 适读对象: 需要入门Core Data的朋友; 像我一样,尚未学过数据库相关课程,不太懂怎么写SQLite语句的朋...
    AntonyWong阅读 5,104评论 8 21
  • 本文我们将会更加深入探讨Core Data 的models以及managed object的类 。本文绝不是对 C...
    评评分分阅读 555评论 1 6
  • 你我之间本无缘分,全靠我一个人死撑,我明白的。” 看见这句话,愣了一下。太狠了。 你们说,世界上真正平等付出的有几...
    星晴梓念阅读 806评论 0 1
  • “我们现在经历的那些,无论好的坏的,全部都是微不足道的小事。度过那些摇摇晃晃的日子,所有艰辛也会内化成为力量,留下...
    chenum1阅读 452评论 0 1
  • 今日所学: 生活有10%是由发生在你身上的事组成的,而另外的90%,取决于你对事情的反应。保持良好的心态。 不想当...
    磐石yy阅读 168评论 0 0