本文我們將會(huì)更加深入探討Core Data 的 models
以及 managed object
的類 。本文絕不是對(duì) Core Data 的簡(jiǎn)單概述,而是在實(shí)際運(yùn)用中鮮為人知或不易記憶卻可以發(fā)揮奇效的那一部分的合集。如果你需要的是更詳細(xì)的概述,那么我推薦你去看 “Apple’s Core Data Programming Guid”。
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í)體通過 NSAttributeDescription
和 NSRelationshipDescription
對(duì)象來表示實(shí)體屬性和實(shí)體之間的關(guān)系。雖然你幾乎不需要去處理這些事情,但是知道這些類總有好處。
一旦就創(chuàng)建了某個(gè)實(shí)體,我們就需要去定義該實(shí)體的一些屬性。屬性定義是非常簡(jiǎ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)值的可選屬性。
另一個(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。
Core Data 支持包括整形、浮點(diǎn)型、布爾型在內(nèi)的許多常見數(shù)據(jù)類型。但是數(shù)據(jù)模型編輯器默認(rèn)以 NSNumber 生成這些屬性并內(nèi)置于 managed object
子類中。這使得我們經(jīng)常會(huì)在程序代碼中用調(diào)用 floatValue
,boolValue
,integerValue
等 NSNumber
的方法。
當(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í)了了。
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)的持久屬性)這一部分的自定義代碼。
在多個(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é)可以參考蘋果官方文檔。
實(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)系化可以讓你擺脫這些不必要的麻煩。
在為 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í)的瓶頸問題。
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ì)在下面介紹。
所有使用過 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ò)的選擇。
任一使用 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
的子類也應(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)建這些類,即使你需要自己去書寫幾行繁瑣的代碼。
當(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>
在模型類中可以加入一個(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
就可以了。
如果你用 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];
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ā)。
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)單才是王道。