-
Notifications
You must be signed in to change notification settings - Fork 143
Core Data
官方文档为第一手参考资料:Core Data Guide
待填......
陈旧的 Core Data 文档终于在 iOS 9 正式发布的当天更新了,文档更新历史上写着此次更新重写了文档,是关于当前 API 实践的重大更新。然而数据类型的部分大概不怎么想写了,就几段话,而非标准数据类型就剩下下面一段话了,原本关于这部分的内容就我一直很困惑,如今倒好文档也给删了。

之前 google 的缓存还可以看以前的文档,后来缓存也更新了,还好我将旧的缓存备份了一份。

Core Data 支持的标准数据类型包括:
- 不同类型的数字,包括16、32、64位的 Int,两种浮点数,Decimal,布尔值,这些数据都会被封装成 NSNumber,在生成子类的时候可以选择是否使用原始的数据类型;
- String, 使用 Unicode 存储,由于 Unicode 的复杂性,对 String 类型的属性进行排序和搜索会比较复杂;
- Date,仅仅是对
NSTimeInterval(也就是 Double)的封装,不包含时区信息,你需要自行保存时区的信息,默认在子类中使用 NSDate 类型,可以在生成子类时使用原始数据类型,会使用 NSTimeInterval; - BinaryData,也就是 NSData 类型。
- 以上这些标准类型都是不可变的类型,剩下的两种:Undefined 和 Transformable 用于支持非标准类型的数据。对于 Undefined 类型,记得在右侧的 Data Model Inspector 里勾选 Transient 选项。支持非标准类型时,你最好也使用不可变类型,不然 Core Data 无法跟踪数据的变化。也就是说每次给属性设置一个新的值,Core Data 才知道你的
NSManagedObject对象发生了变化,这点在你使用自定义数据类型时很重要。
那不是以上这些类型的对象和结构体怎么办?总体来说,支持非标准类型有两条路:Transformable Attribute 和 Transient Attribute。
使用这种属性时工作量很小,适合遵守 NSCoding 协议的类型,在你生成的 NSManagedObject 子类中将该属性类型更改为你需要的类型即可,剩下的工作由 Core Data 替我们完成。缺点是这种属性的内部存储由于是使用属性列表 property list 这种格式,所以比较浪费空间,在性能上也不太好。如果你对性能和空间没有很特别的要求,使用这种就可以了。

如果你想更加高效地存储,你可以提供自定义的 value transformer 来实现数据的转化。(我是不会这个的)
类似 Swift 里的计算属性,需要根据其他属性来生成。Core Data 在 fetch 时并不会处理这种属性,不会存储这种属性,也不会跟踪这种属性的变化。总之就是对这种属性不管不问,完全由开发者自己负责;不过,处理这种属性的全过程都可以自定义,应对各种需求。

Core Data 对这种属性完全是放任自流的,使用时需要额外的工作:需要我们实现属性的存取方法 accessor methods。 比如,处理 UIImage 对象时,需要先使用一个备份属性使用 Core Data 支持的类型,然后 transient attribute 利用前者生成我们需要的 UIImage 对象。
备注:在Model Editor 里「Allows External Storage」不是必选选项,选中该项后由 Core Data 决定数据是否存放在其他地方;而对于 transient attribute,必须指定 Attribute Type 为 Undefined,而且要选中 Transient。@NSManaged修饰符表示由 Core Data 来负责动态生成存取方法,对于 transient attribute,无法使用@NSManaged修饰符,因为这是需要开发者负责的。
使用 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:上面的代码里 image 属性就是例子。
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:上面的 image 属性就是。
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)
}
在上一节中,使用了 NSData 来存储图像的二进制数据,这里也可以不用 transient attribute 这种方式,直接用 NSData 存储。如果直接使用二进制来存储数据,那么有几个问题需要考虑。
首先是内存占用,图像一般比其他数据大得多。下面是 Core Data 支持的 persistent store 类型,面对会占用大量内存的数据,使用 SQLite store 是最佳选择。

其次,选择更合理的访问策略。与其将 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 在 fetch 后获取对应的 CGSize 数据了,这个手法和 Swift 里的计算属性相似。
不支持 NSCoding 协议的类,可以考虑将这个类用NSManagedObject子类来封装,其内部被 Core Data 支持的数据就不要说了,不被直接支持的数据就拆分成支持的类型。