鍍金池/ 教程/ iOS/ 數(shù)據(jù)模型和模型對(duì)象
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

數(shù)據(jù)模型和模型對(duì)象

本文我們將會(huì)更加深入探討Core Data 的 models 以及 managed object 的類 。本文絕不是對(duì) Core Data 的簡(jiǎn)單概述,而是在實(shí)際運(yùn)用中鮮為人知或不易記憶卻可以發(fā)揮奇效的那一部分的合集。如果你需要的是更詳細(xì)的概述,那么我推薦你去看 “Apple’s Core Data Programming Guid”。

數(shù)據(jù)模型

Core Data數(shù)據(jù)模型(儲(chǔ)存在 *.xcdatamodel 文件里)中定義了數(shù)據(jù)類型 (在 Core Data 里的“實(shí)體”中)。大多數(shù)情況下,我們更偏向通過 Xcode 的圖形界面去定義一個(gè)數(shù)據(jù)模型,但同樣我們可以使用純代碼去完成這個(gè)工作。首先,你需要?jiǎng)?chuàng)建一個(gè) NSManagenObjectModel 對(duì)象,然后創(chuàng)建 NSEntitiyDesciption 對(duì)象來表示一組實(shí)體,該實(shí)體通過 NSAttributeDescriptionNSRelationshipDescription 對(duì)象來表示實(shí)體屬性和實(shí)體之間的關(guān)系。雖然你幾乎不需要去處理這些事情,但是知道這些類總有好處。

屬性 (Attributes)

一旦就創(chuàng)建了某個(gè)實(shí)體,我們就需要去定義該實(shí)體的一些屬性。屬性定義是非常簡(jiǎn)單的,但是接下來我們要深入的研究屬性的某些特性。

默認(rèn)的/可選的

每個(gè)屬性可被定義成可選的或者非可選(必須)的。如果一個(gè)被變更的對(duì)象的非可選屬性沒有設(shè)置的話,那么在存儲(chǔ)時(shí)將會(huì)失敗。同時(shí),我們可以為每一個(gè)屬性設(shè)置默認(rèn)值。沒有人阻止我們使用一個(gè)可選的屬性并且給它賦一個(gè)默認(rèn)的值,但是當(dāng)你進(jìn)行深入思考的時(shí)候,你會(huì)發(fā)現(xiàn)這么做并沒有什么意義甚至引起混淆。所以,我們建議永遠(yuǎn)不要使用帶有默認(rèn)值的可選屬性。

瞬態(tài) Transient

另一個(gè)經(jīng)常被忽視的屬性的特性選項(xiàng)是它的 transient 。被聲明為 transient 的屬性除了不被持久化到本地之外,其余所有行為都與正常屬性類似。這也意味著可以對(duì)它們進(jìn)行校驗(yàn),撤銷管理,故障處理等操作。當(dāng)你將更復(fù)雜的數(shù)據(jù)模型邏輯映射到 managed object subclasses 的時(shí)候, transient 屬性將會(huì)發(fā)揮出它的優(yōu)勢(shì)。我們將會(huì)在后面繼續(xù)討論這個(gè)特性,以及我們更傾向于使用 transient 屬性而非實(shí)例變量的原因。

索引

如果你以前使用過關(guān)系數(shù)據(jù)庫,那么你對(duì)索引應(yīng)該并不陌生。如果沒有,你可以認(rèn)為屬性的索引可以提供一種大幅提高檢索速度的方法。但是有利有弊,索引在提高讀取速度的同時(shí)卻降低了寫入速度,因?yàn)槊慨?dāng)數(shù)據(jù)變更的時(shí)候,索引就需要進(jìn)行相應(yīng)的更新。

當(dāng)把一個(gè)屬性設(shè)置為 indexed 時(shí),它將在 SQLite 中所對(duì)應(yīng)的表的列中建立索引。我們能夠?yàn)槿魏螌傩詣?chuàng)建索引,但是請(qǐng)留意對(duì)寫性能的潛在影響。Core Data 當(dāng)然也支持創(chuàng)建復(fù)合索引(在 entity 的 檢查器的 Indexs 部分中),就像那些橫跨了多個(gè)屬性的索引。當(dāng)你在多屬性的場(chǎng)景下使用復(fù)合索引來獲取數(shù)據(jù)時(shí)可以對(duì)檢索效率進(jìn)行提升。Daniel 有一個(gè)使用復(fù)合索引獲取數(shù)據(jù)的例子:fetching data。

標(biāo)量類型

Core Data 支持包括整形、浮點(diǎn)型、布爾型在內(nèi)的許多常見數(shù)據(jù)類型。但是數(shù)據(jù)模型編輯器默認(rèn)以 NSNumber 生成這些屬性并內(nèi)置于 managed object 子類中。這使得我們經(jīng)常會(huì)在程序代碼中用調(diào)用 floatValueboolValue,integerValueNSNumber 的方法。

當(dāng)然,我們同樣可以直接設(shè)置這些屬性為想要的標(biāo)量類型,如 int64_t,float_t 或是 BOOL,它們一樣可以正常運(yùn)作。XCode甚至在生成 NSManagedObject(原始數(shù)據(jù)類型使用標(biāo)量屬性)對(duì)話框內(nèi)有一個(gè)小選擇框可以為你進(jìn)行強(qiáng)類型匹配。 取而代之,不再會(huì)是:

@property (nonatomic, strong) NSNumber *myInteger;

而會(huì)用如下聲明替換:

@property (nonatomic) int64_t myInteger;

這就是我們?cè)?Core Data 中進(jìn)行獲取和保存標(biāo)量類型所需要做的全部。在文檔中,仍然規(guī)定 Core Data 將不能自動(dòng)的為標(biāo)量生成存取方法,現(xiàn)在看來文檔似乎有點(diǎn)過時(shí)了了。

存儲(chǔ)其他類型對(duì)象

Core Data 并沒有約束我們只能對(duì)預(yù)定義類型進(jìn)行存儲(chǔ)。事實(shí)上,對(duì)于任何遵守 NSCoding 協(xié)議的對(duì)象甚至到任何包含了大量功能的結(jié)構(gòu)對(duì)象,我們都可以對(duì)其進(jìn)行輕松的存儲(chǔ)。

我們可以通過使用 transformable attributes 來存儲(chǔ)遵守 NSCoding 協(xié)議的對(duì)象。我們需要做的僅僅是在下拉菜單中選擇 “Transformable” 選項(xiàng)。如果你生成了一個(gè)相應(yīng)的 managed object subclasses,你就會(huì)看到一個(gè)類似如下的屬性聲明:

@property (nonatomic, retain) id anObject;

我們可以手動(dòng)將對(duì)象原有的 id 類型修改成任意我們想要儲(chǔ)存的類型來使編輯器進(jìn)行強(qiáng)類型檢查。然而在使用 transformable 屬性的時(shí)候我們會(huì)遇到一個(gè)陷阱:如果我們想使用默認(rèn)轉(zhuǎn)換器(最常用的),我們必須不能為它指定名字。甚至指定默認(rèn)轉(zhuǎn)換器名字為其原始名字(NSKeyedUnarchiveFromDataTransformerName)都將會(huì)導(dǎo)致不好的事情。

不僅限于此,我們還可以創(chuàng)建自定義的值轉(zhuǎn)換器并使用它們?nèi)ゴ鎯?chǔ)任意的對(duì)象類型。只要我們能夠把要存儲(chǔ)的東西轉(zhuǎn)化為可支持的基本類型,我們就能存儲(chǔ)它。為了儲(chǔ)存比如結(jié)構(gòu)體這種不支持的非對(duì)象的類型,基本的解決方式是創(chuàng)建一個(gè)未定義類型的 transient 屬性和一個(gè)持久化的已支持類型的影子屬性。然后,重寫 transient 屬性的存取方法,將值轉(zhuǎn)化為上述的持久化類型。這是很重要的,因?yàn)檫@些存取方法需要遵從 KVC/KVO,同時(shí)還需要考慮到 Core Data 原始存取方法。請(qǐng)閱讀蘋果指南中的 non-standard persistent attributes(非標(biāo)準(zhǔn)的持久屬性)這一部分的自定義代碼

抓取屬性 (Fetched Properties)

在多個(gè)持久化存儲(chǔ)之間創(chuàng)建關(guān)系的時(shí)候我們經(jīng)常會(huì)用到 fetched 屬性。由于使用多個(gè)持久化存儲(chǔ)本身已經(jīng)是非常不常見、且高級(jí)的案例,因此 fetched 屬性幾乎也不會(huì)被使用。

當(dāng)我們獲取一個(gè)抓取屬性的時(shí)候,Core Data 會(huì)在后端執(zhí)行一個(gè)抓取請(qǐng)求并且緩存抓取結(jié)果。我們可以直接在 Xcode 中數(shù)據(jù)模型編輯器里通過指定目標(biāo)實(shí)體類型和斷言來對(duì)抓去請(qǐng)求進(jìn)行配置。這里的斷言是動(dòng)態(tài)的而非靜態(tài)的,其通過 $FETCH_SOURCE 和 $FETCHED_PROPERTY 兩個(gè)變量在程序運(yùn)行態(tài)進(jìn)行配置。更多細(xì)節(jié)可以參考蘋果官方文檔。

關(guān)系 (Relationships)

實(shí)體間的關(guān)系應(yīng)該總是被定義成雙向的。這給予了 Core Data 足夠的信息為我們?nèi)婀芾眍悎D。盡管定義雙向的關(guān)系不是一個(gè)硬性要求,但我還是強(qiáng)烈建議這么去做。

如果你對(duì)實(shí)體之間的關(guān)系很了解,你也能將實(shí)體定義成單向的關(guān)系,Core Data 不會(huì)有任何警告。但是一旦這么做了,你就必須承擔(dān)很多正常情況理應(yīng)由 Core Data 管理的一些職責(zé),包括確認(rèn)圖形對(duì)象的一致性,變化跟蹤和撤銷管理。舉一個(gè)簡(jiǎn)單的例子,我們有“書”和“作者”兩個(gè)實(shí)體,并設(shè)置了一個(gè)書到作者的單項(xiàng)關(guān)系。當(dāng)我們刪除了“作者”的時(shí)候,和這個(gè)“作者”有關(guān)聯(lián)的“書”將無法收到這個(gè)“作者”被刪除的消息。此后,我們?nèi)耘f可以使用這本書“作者”的關(guān)系,只是我們將會(huì)得到一個(gè)指向空的錯(cuò)誤。

很明顯單向關(guān)系帶來的弊端絕對(duì)不會(huì)是你想要的。雙向關(guān)系化可以讓你擺脫這些不必要的麻煩。

數(shù)據(jù)類型設(shè)計(jì)

在為 Core Data 設(shè)計(jì)數(shù)據(jù)模型的時(shí)候,一定要牢記 Core Data 不是一個(gè)關(guān)系數(shù)據(jù)庫。因此,我們?cè)谠O(shè)計(jì)數(shù)據(jù)模型的時(shí)候只需要著眼于數(shù)據(jù)將要如何組織和展示即可,而不是像設(shè)計(jì)數(shù)據(jù)庫表一樣來進(jìn)行設(shè)計(jì)。

在需要對(duì)某一數(shù)據(jù)進(jìn)行展示的時(shí)候避免大規(guī)模的抓取該數(shù)據(jù)的關(guān)系數(shù)據(jù),從這一點(diǎn)看通常數(shù)據(jù)模型的非規(guī)范化是有其價(jià)值的。再舉個(gè)例子,假如現(xiàn)有一個(gè)“作者”實(shí)體中有一個(gè)一對(duì)多的關(guān)系指向“書”實(shí)體的話,如果我們只需要展示作者寫的書的數(shù)量的話,再保存一個(gè)數(shù)字會(huì)是一個(gè)很好的做法。

這是因?yàn)?,假設(shè)我們需要展示一張作者和其對(duì)應(yīng)作品數(shù)量的表。如果取得每個(gè)作者名下作品的數(shù)量這條數(shù)據(jù)只能通過統(tǒng)計(jì)作者實(shí)體關(guān)聯(lián)的書實(shí)體的數(shù)量來獲取,則每一個(gè)作者單元格中必須進(jìn)行一次抓取請(qǐng)求操作。這樣做性能不佳。我們可以使用 relationshipKeyPathsForPrefetching 對(duì)書對(duì)象進(jìn)行預(yù)抓取,但當(dāng)保存的書數(shù)據(jù)量大的時(shí)候,這同樣無法達(dá)到理想狀態(tài)。所以,如果我們?yōu)槊總€(gè)作者添加一個(gè)屬性來管理書籍?dāng)?shù)量,那么,一切所需信息都將在請(qǐng)求抓取作者信息的時(shí)候一并獲得。

當(dāng)然,為保持冗余數(shù)據(jù)的同步,非規(guī)范化也會(huì)帶來額外的性能開銷。我們需要根據(jù)實(shí)際情況來權(quán)衡是否需要這么做。有時(shí)這么做不會(huì)有什么感覺,但有時(shí)其帶來的麻煩會(huì)讓你頭疼不已。這樣做非常依賴于特定的數(shù)據(jù)模型,比如應(yīng)用有沒有需要去與后臺(tái)交互,或者是否要在多個(gè)客戶端之間使用點(diǎn)對(duì)點(diǎn)的形式同步數(shù)據(jù)。

通常情況下,這個(gè)數(shù)據(jù)模型已經(jīng)被某個(gè)后臺(tái)服務(wù)定義過了,我們可能只需要將數(shù)據(jù)模型復(fù)制到應(yīng)用程序即可。然而,即使在這種情況下,我們?nèi)杂袡?quán)利在客戶端對(duì)數(shù)據(jù)模型進(jìn)行一些修改,就比如我們可以為后臺(tái)數(shù)據(jù)模型定義一個(gè)清晰的映射。再拿“書”和“作者”舉例,僅在客戶端執(zhí)行向作者實(shí)體添加一個(gè)作品數(shù)量屬性的小操作以實(shí)現(xiàn)檢索性能的優(yōu)化而無需通知服務(wù)器。如果我們做了一些本地修改或從服務(wù)器接收到了新的數(shù)據(jù),我們需要更新這些屬性并且保持其余的數(shù)據(jù)同步。

實(shí)際情況往往復(fù)雜的多,但就像上面的簡(jiǎn)單優(yōu)化,卻能緩解在處理標(biāo)準(zhǔn)關(guān)系數(shù)據(jù)庫數(shù)據(jù)模型的性能時(shí)的瓶頸問題。

實(shí)體層級(jí) vs 類層級(jí)

Managed object models 可以允許創(chuàng)建實(shí)體層級(jí),即我們可以指定一個(gè)實(shí)體繼承另外一個(gè)實(shí)體。雖然,實(shí)體間可以通用一些都有的屬性聽起來不錯(cuò),不過在實(shí)踐中我們幾乎不會(huì)這么去做。

這一切背后發(fā)生的事情其實(shí)是,Core Data 將所有帶有相同父實(shí)體的實(shí)體存儲(chǔ)在同一張表中。這樣做會(huì)迅速的建成一個(gè)含有大量屬性的數(shù)據(jù)表,并使性能降低。通常情況下,我們創(chuàng)建實(shí)體層級(jí)的目的僅僅是為了創(chuàng)建一個(gè)類層級(jí),從而可以在實(shí)體基類中實(shí)現(xiàn)代碼并分享到多個(gè)子類實(shí)體中。當(dāng)然,我們還有更好的方法來實(shí)現(xiàn)這個(gè)需求。

實(shí)體層級(jí)與 NSManagedObject 父類層級(jí)是相互獨(dú)立的。換言之,我們不需要去為了已有的實(shí)體層級(jí)而去創(chuàng)建一個(gè)類層級(jí)。

讓我們繼續(xù)用“作者”和“書”舉例。他們兩者間會(huì)有一些共有的字段,比如ID(identifier),創(chuàng)建時(shí)間(createdAt),修改時(shí)間(changedAt)。我們可以為這個(gè)例子構(gòu)建如下的結(jié)構(gòu):

Entity hierarchy            Class hierarchy
----------------            ---------------

   BaseEntity                 BaseEntity
    |      |                   |      |
 Authors  Books             Authors  Books

然而,我們可以壓縮實(shí)體的層級(jí)關(guān)系而保持類的層級(jí)關(guān)系不變。

 Entity hierarchy            Class hierarchy
 ----------------            ---------------

  Authors  Books               BaseEntity
                                |      |
                             Authors  Books

這個(gè)類可能會(huì)被這樣聲明:

 @interface BaseEntity : NSManagedObject
 @property (nonatomic) int64_t identifier;
 @property (nonatomic, strong) NSDate *createdAt;
 @property (nonatomic, strong) NSDate *changedAt;
 @end

 @interface Author : BaseEntity
 // Author specific code...
 @end

 @interface Book : BaseEntity
 // Book specific code...
 @end

這樣做的好處是我們能夠?qū)⒐餐拇a移動(dòng)到父類中,同時(shí)避免了將所有實(shí)體放到放到一個(gè)表中引起的性能消耗。雖然我們?cè)?Xcode 的管理對(duì)象生成器中無法根據(jù)實(shí)體層級(jí)來創(chuàng)建類層級(jí),但是花費(fèi)極少的代價(jià)去手動(dòng)的去創(chuàng)建管理對(duì)象類將會(huì)給我們巨大好處,具體我們會(huì)在下面介紹。

配置與抓取請(qǐng)求模板

所有使用過 Core Data 的人肯定都與數(shù)據(jù)模型的“實(shí)體-模型”方面的功能打過交道。但是數(shù)據(jù)模型還有兩個(gè)相對(duì)少見少用的領(lǐng)域:配置 (configurations) 和 抓取請(qǐng)求模板 (fetch request templates)。

配置用來定義是哪個(gè)實(shí)體需要保存在哪個(gè)持久化存儲(chǔ)。持久化存儲(chǔ)協(xié)調(diào)器使用 addPersistentStoreWithType:configuration:URL:options:error: 來添加持久化存儲(chǔ),其中配置參數(shù)定義了需要映射的持久化存儲(chǔ)。在目前所有的應(yīng)用場(chǎng)景中,我們只會(huì)使用一個(gè)持久化儲(chǔ)存,因此不用考慮處理多個(gè)配置的情況。當(dāng)創(chuàng)建好一個(gè)持久化存儲(chǔ)的時(shí)候默認(rèn)的配置就已經(jīng)為我們配置好了。其實(shí)還是有多存儲(chǔ)的極為少見的實(shí)例的,本話題的 導(dǎo)入大數(shù)據(jù)集 一文中對(duì)其進(jìn)行了概述。

正如抓取請(qǐng)求模板的名字所暗示的那樣:預(yù)定義的抓取請(qǐng)求以 managed object model 的形式存儲(chǔ),需要時(shí)可以執(zhí)行 fetchRequestFormTemplateWithName:substitutionVariables 操作從而方便地使用。我們可以使用 Xcode 中數(shù)據(jù)模型編輯器或者代碼來定義這些模板。雖然 Xcode 的編輯器還不能夠支持 NSFetchRequest 的所有功能。

老實(shí)說我曾經(jīng)有一段痛苦的經(jīng)歷去說服別人使用抓取請(qǐng)求模板。其實(shí)一個(gè)好處是抓取請(qǐng)求的斷言將會(huì)被預(yù)先解析好,從而當(dāng)你執(zhí)行一條新的抓取請(qǐng)求的時(shí)候該步驟不用每次執(zhí)行。雖然幾乎沒有什么聯(lián)系,任何頻繁的抓取都會(huì)使我們陷入麻煩之中。假如你在找一個(gè)定義你抓取請(qǐng)求的地方(你不應(yīng)該將它們定義在視圖控制器中),也許考慮將它們儲(chǔ)存在于 managed object model 中將會(huì)是個(gè)不錯(cuò)的選擇。

Managed Objects

任一使用 Core Data 的應(yīng)用其核心就是 managed objects。managed objects 依賴 managed object context 而存在并反映我們的數(shù)據(jù)。managed objects 理應(yīng)在程序中至少穿透 model-controller 的壁壘,甚至?xí)┩?controller-view 的壁壘,而被分發(fā)。盡管后者頗具爭(zhēng)議但是我們可以更好的通過一個(gè)例子來進(jìn)行抽象理解:定義一個(gè)協(xié)議,遵守該協(xié)議的對(duì)象可以被某個(gè)視圖使用,或者是通過在視圖的類別中實(shí)現(xiàn)配置方法來橋接數(shù)據(jù)對(duì)象與特定的視圖之間的間隙。

不管怎么說我都不能將 managed objects 限定于數(shù)據(jù)層,當(dāng)我們想分發(fā)數(shù)據(jù)的時(shí)候,應(yīng)該將它們及時(shí)抓取出來并放入不同結(jié)構(gòu)中去。managed objects 是 Core Data 應(yīng)用中的一等公民,所以我們也要將它們用得適得其所。舉個(gè)例子,managed objects 應(yīng)該在兩個(gè)視圖控制器間進(jìn)行傳遞,并為它們提供所需要的數(shù)據(jù)。

為了獲取 managed objects context 我們經(jīng)常在代碼中看到如下代碼:

NSManagedObjectContext *context = 
  [(MyApplicationDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];

如果你已經(jīng)給視圖控制器傳遞了一個(gè)模型對(duì)象,可以直接通過對(duì)象來獲取上下文:

NSManagedObjectContext *context = self.myObject.managedObjectContext;

這么做移除了 application delegate 的隱性依賴并且增強(qiáng)了代碼可讀性以及更便于測(cè)試。

使用 Managed object 子類

類似的,managed object 的子類也應(yīng)當(dāng)這樣被使用。我們可以在這些類中實(shí)現(xiàn)自定義業(yè)務(wù)邏輯,驗(yàn)證邏輯和輔助方法,同時(shí)創(chuàng)建層級(jí)以便于剝離出共同的代碼放進(jìn)父類中。后者的實(shí)現(xiàn)非常簡(jiǎn)單,因?yàn)轭惖膶蛹?jí)和實(shí)體層級(jí)的解耦合在上面已經(jīng)提過了。

你可能想知道,當(dāng) Xcode 在重新生成文件的時(shí)候總是覆蓋它們,那么如何在managed object 子類中實(shí)現(xiàn)自定義代碼。其實(shí),這個(gè)答案十分簡(jiǎn)單,不要使用 Xcode 生成它們即可。如果你仔細(xì)想想,在這些類中被生成的代碼很瑣碎并且你自己也很容易能夠?qū)崿F(xiàn),當(dāng)然你也可以只生成一次然后保證手動(dòng)更新就好。因?yàn)樯傻闹皇且欢褜傩缘穆暶鳌?/p>

當(dāng)然還有一些其他的解決方案,如將自定義代碼放到類別中,或者使用類似 mogenerator 這樣的工具。mogenerator 可以為每個(gè)實(shí)體和子類創(chuàng)建一個(gè)可以支持用戶代碼的基礎(chǔ)類。但是,上面的所有解決方案都不能根據(jù)實(shí)體層級(jí)靈活的創(chuàng)建類層級(jí)。所以我們還是建議你手動(dòng)創(chuàng)建這些類,即使你需要自己去書寫幾行繁瑣的代碼。

Managed Object 子類中的實(shí)例變量

當(dāng)我們開始使用 managed object 子類來實(shí)現(xiàn)業(yè)務(wù)邏輯的時(shí)候,我們可能會(huì)需要?jiǎng)?chuàng)建一些實(shí)例變量來緩存計(jì)算結(jié)果之類的東西。為了方便的達(dá)到這個(gè)目的,我們可以使用 transient 。因?yàn)?managed object 的生命周期與一般的對(duì)象有一點(diǎn)不同。Core Data 經(jīng)常會(huì)對(duì)那些不再需要的對(duì)象執(zhí)行 faults 操作。如果我們要使用實(shí)例變量,就必須將其手動(dòng)加入進(jìn)程并且釋放這些實(shí)例變量。然而,當(dāng)我們換成 transient 屬性,這一切都不再需要我們?nèi)プ隽恕?/p>

創(chuàng)建新對(duì)象

在模型類中可以加入一個(gè)類方法來將新的對(duì)象插入到 managed object 上下文中,這是在模型類中添加有用輔助方法的一個(gè)好例子。Core Data 創(chuàng)建新對(duì)象的 API 并不是非常的直觀:

Book *newBook = [NSEntityDescription insertNewObjectForEntityForName:@"Book"    inManagedObjectContext:context];

萬幸的是,我們能夠輕易的在我們的子類中以一個(gè)優(yōu)雅的方式解決這個(gè)問題:

@implementation Book
// ...

+ (NSString *)entityName
{
    return @"Book"
}

+ (instancetype)insertNewObjectIntoContext:(NSManagedObjectContext *)context
{
    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName]
                                         inManagedObjectContext:context];
}
@end

現(xiàn)在,創(chuàng)建一個(gè)“書”對(duì)象就簡(jiǎn)單得多。

Book *book = [Book insertNewObjectIntoContext:context];

當(dāng)然,如果我們將實(shí)際模型類從共同的父類中繼承下來,我們就應(yīng)當(dāng)將 insertNewObjectIntoContext:entityName 這兩個(gè)方法移動(dòng)到父類中。然后每個(gè)子類里面就只需要去重寫 entityName 就可以了。

一對(duì)多關(guān)系賦值

如果你用 Xcode 生成了一個(gè)含有一對(duì)多關(guān)系的 managed object 子類的話,系統(tǒng)將會(huì)為我們創(chuàng)建在這個(gè)關(guān)系中增刪對(duì)象的方法。

- (void)addBooksObject:(Book *)value;
- (void)removeBooksObject:(Book *)value;
- (void)addBooks:(NSSet *)values;
- (void)removeBooks:(NSSet *)values;

我們有一個(gè)更加優(yōu)雅的方法來替代這些賦值方法,尤其是在我們沒有生成 managed object 子類的情況下。我們可以簡(jiǎn)單的使用 mutableSetValueForKey: 方法獲取相關(guān)的可變對(duì)象的集合(或者對(duì)于有序關(guān)系的話,使用mutableOrderedSetValueForKey:)。這樣可以封裝成為一個(gè)更簡(jiǎn)單的存取方法:

- (NSMutableSet *)mutableBooks
{
    return [self mutableSetValueForKey:@"books"];
}

然后我們可以如同使用一般的集合那樣使用這個(gè)可變集合。Core Data 將會(huì)捕捉到這些變換,并且?guī)臀覀兲幚硎O碌氖虑椤?/p>

Book *newBook = [Book insertNewObjectIntoContext:context];
[author.mutableBooks addObject:newBook];

驗(yàn)證

Core Data 支持多種數(shù)據(jù)驗(yàn)證的方式。Xcode 的數(shù)據(jù)模型編輯器讓我們?yōu)閷傩灾贫ㄒ恍┗镜男枨螅拖褚粋€(gè)字符串的最大和最小長(zhǎng)度,或者是一對(duì)多關(guān)系中最多和最少的對(duì)象個(gè)數(shù)。除此之外,使用代碼我們可以做更多的事情。

文檔中 “Managed Object Validation” 一節(jié)對(duì)本主題有更加深入的介紹。Core Data 通過實(shí)現(xiàn) validate<Key>:error: 方法支持屬性層級(jí)驗(yàn)證,以及通過 validateForInsert:, validateForUpdate:,和 validateForDelete: 方法進(jìn)行屬性內(nèi)驗(yàn)證。驗(yàn)證將會(huì)在保存前自動(dòng)進(jìn)行,當(dāng)然我們也可以在屬性層使用 validateValue:forKey:error:方法手動(dòng)觸發(fā)。

總結(jié)

Core Data 應(yīng)用依賴于數(shù)據(jù)模型與模型對(duì)象。我們鼓勵(lì)大家不要去直接使用便利的封裝,而是去擁抱 managed object 子類和對(duì)象。當(dāng)使用 Core Data 的時(shí)候,值得非常注意的是要十分清楚發(fā)生了什么,否則的話一旦你的應(yīng)用變得復(fù)雜的時(shí)候,事情就會(huì)很糟糕了。

我們希望已闡述的幾個(gè)簡(jiǎn)單的技術(shù)可以使你更容易的使用 managed objects。另外我們還初步了解了幾個(gè)非常高級(jí)的特性,以便大家在使用數(shù)據(jù)對(duì)象的時(shí)候?qū)ξ覀兡茏龅叫┦裁从袀€(gè)大致的概念。請(qǐng)謹(jǐn)慎地運(yùn)用這些技術(shù),因?yàn)榈筋^來其實(shí)往往還是簡(jiǎn)單才是王道。

上一篇:DTrace下一篇:View-Layer 協(xié)作