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」?!窤llows 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 來連接前者。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 適讀對象: 需要入門Core Data的朋友; 像我一樣,尚未學過數據庫相關課程,不太懂怎么寫SQLite語句的朋...
    AntonyWong閱讀 5,327評論 8 21
  • 本文我們將會更加深入探討Core Data 的models以及managed object的類 。本文絕不是對 C...
    評評分分閱讀 574評論 1 6
  • 你我之間本無緣分,全靠我一個人死撐,我明白的?!?看見這句話,愣了一下。太狠了。 你們說,世界上真正平等付出的有幾...
    星晴梓念閱讀 833評論 0 1
  • “我們現在經歷的那些,無論好的壞的,全部都是微不足道的小事。度過那些搖搖晃晃的日子,所有艱辛也會內化成為力量,留下...
    chenum1閱讀 483評論 0 1
  • 今日所學: 生活有10%是由發生在你身上的事組成的,而另外的90%,取決于你對事情的反應。保持良好的心態。 不想當...
    磐石yy閱讀 194評論 0 0