這一部分包含了遷移的簡介,回答了遷移是什么,什么時(shí)候要遷移的問題窥突,寫了輕量級(jí)遷移 和 基于 MappingModel 的手動(dòng)遷移。
純手動(dòng)遷移 和 漸進(jìn)式遷移 放在后面硫嘶。
0. 前言概述
發(fā)布應(yīng)用新版本的時(shí)候經(jīng)常會(huì)遇到這樣頭痛的問題阻问,新版本的 Core Data 的數(shù)據(jù)模型要增加新的屬性,當(dāng)然你要對(duì) Core Data 的 Data Model 做出修改沦疾,Core Data 的遷移就是為了解決這樣的問題称近。
關(guān)于遷移渠道呢第队?Migration 會(huì)使用 映射 或 匹配,從而使得舊的 Data Model 升級(jí)為新的 Data Model刨秆。
簡單來說凳谦,任何時(shí)候你的應(yīng)用程序都會(huì)有新的變化,然而并不是每一次變更都不會(huì)更新 Data Model衡未,當(dāng)它改變時(shí)尸执,你需要提供新的 Data Model,并且提供遷移途徑缓醋。
1. Core Data 遷移概述
Core Data 進(jìn)行遷移如失,首先要把握時(shí)機(jī)。
那么在什么時(shí)機(jī)發(fā)生遷移呢送粱?在 Core Data 在程序中初始化的時(shí)候褪贵,會(huì)創(chuàng)建我那篇文章的文章中提到的Core Data Stack悉尾,反正不管怎么樣初始化咆爽,它初始化一定會(huì)把 Sotre 添加進(jìn)存儲(chǔ)化存儲(chǔ)助手也就是NSPersistentStoreCoordinator
,然而遷移就是在添加這個(gè)之前發(fā)生的稚叹。
然后 Core Data 已經(jīng)把握好了這個(gè)時(shí)機(jī)动雹,他就要考慮現(xiàn)在我要不要遷移呢槽卫?
這個(gè)問題。只有一種情況下會(huì)遷移洽胶,Store 的 Model 版本跟當(dāng)前的存儲(chǔ)助手配置的 Model 版本不相符的時(shí)候
晒夹。當(dāng)然話說回來不相符之后你還得啟用遷移開關(guān)才行,開關(guān)沒開的話就報(bào)錯(cuò)姊氓。
那么如果開始執(zhí)行遷移:首先去分析原先的 Data Model 和你現(xiàn)在的目標(biāo) Data Model丐怯,使用這兩個(gè) Model 去創(chuàng)建 遷移映射模型(Migration mapping model),這個(gè)映射模型用來把舊的Model里面的數(shù)據(jù)轉(zhuǎn)移到新的Model里面翔横。
遷移的三個(gè)步驟:
- Core Data 把舊的 Store 里面的全部數(shù)據(jù)拷貝到新的一個(gè) Store 中读跷。
- Core Data 根據(jù)關(guān)系映射把所有的數(shù)據(jù) Object 關(guān)聯(lián)起來。
- 會(huì)在目標(biāo) Model 中加上數(shù)據(jù)驗(yàn)證禾唁。所以在拷貝過程中 Core Data 禁用了數(shù)據(jù)驗(yàn)證效览。
另外,只有在遷移沒有任何報(bào)錯(cuò)的情況下荡短,遷移執(zhí)行完成丐枉,舊的 Store 才會(huì)被刪除。
1.1 遷移類型
輕量級(jí)遷移: 最簡單的遷移掘托,用戶只需要設(shè)計(jì)幾個(gè)標(biāo)記瘦锹,遷移就會(huì)自動(dòng)完成。比如我們僅僅是添加實(shí)體模型和可選屬性的時(shí)候,
這個(gè)時(shí)候數(shù)據(jù)層的變動(dòng)簡單到根本不需要你去告訴 Core Data 如何遷移弯院,也就是它會(huì)自動(dòng)遷移
辱士,NSPersistentStoreCoordinator 就會(huì)自動(dòng)生成 Mapping Model。如果有更復(fù)雜的變化听绳,就需要自己自定義 Mapping Model 了颂碘。下文中羅列了適合輕量級(jí)遷移的各種情況。手動(dòng)遷移: 手動(dòng)遷移需要自己實(shí)現(xiàn) Mapping 部分的工作椅挣,有 GUI 的工具可以完成头岔,可以自定義映射規(guī)則,你可以做的事情更多鼠证。比如如果你想從一個(gè) Model 中將特殊的屬性單獨(dú)拉出來創(chuàng)建一個(gè) Model切油,這個(gè)時(shí)候手動(dòng)遷移就是首選。
自定義遷移: 如果你需要快速地在你的數(shù)據(jù)模型上進(jìn)行相對(duì)復(fù)雜的改變名惩,那么自定義遷移就是為你準(zhǔn)備的。需要代碼實(shí)現(xiàn)一些對(duì)數(shù)據(jù)的遷移邏輯孕荠。這里自定義的實(shí)體遷移邏輯需要自定義一個(gè) NSEntityMigrationPolicy 子類來實(shí)現(xiàn)遷移邏輯娩鹉。
純手動(dòng)遷移: 當(dāng)自定義的遷移還是不足以滿足需求的時(shí)候,需要用戶自定義 版本檢測的邏輯 和 遷移邏輯稚伍。
2. 輕量遷移
接下來的步驟走一遍簡單的輕量遷移:
創(chuàng)建一個(gè)新的數(shù)據(jù)版本
:在開始做改動(dòng)之前弯予,就是你還沒有修改你的第一版本的時(shí)候,選中 xcdatamodeld
文件然后選擇 Editor
个曙,選擇 Add Model Version
锈嫩,給新版本起一個(gè)名字,最簡單的--在原名字后加個(gè) v2垦搬。
可以在文件查看窗口(File Inspector pane)對(duì) xcdatamodeld 文件的 Version 進(jìn)行查看和更改『舸纾現(xiàn)在切換到剛剛創(chuàng)建的新的版本。
在新的版本中選擇一個(gè) Entity猴贰,給這個(gè)實(shí)體上添加一個(gè)新的 Property对雪,名字按需求來,類型選擇 Transformable米绕。
點(diǎn)擊新的屬性瑟捣,在右邊的編輯窗口的第三個(gè)數(shù)據(jù)模型查看器(Data Model Inspector),在上面的 Attribute Type 下面有一個(gè)Name栅干,輸入:YourProjectName.ImageTransformer(按照圖片類型舉例)迈套。
然后去這個(gè)實(shí)體的 .swift 中添加屬性:
@NSManaged var image: UIImage!
這個(gè)時(shí)候如果運(yùn)行了程序,一定會(huì)報(bào)錯(cuò)吧碱鳞。因?yàn)榍把圆糠终f了桑李,如果 CoreData 發(fā)現(xiàn)你實(shí)際存在的 Model 和你程序里面定義的Model不一致的話,它就會(huì)表示這兩個(gè)不匹配,然后拋錯(cuò)芙扎。所以先激活輕量遷移星岗。
激活輕量遷移
:
激活的時(shí)候你需要在初始化的時(shí)候加兩個(gè)標(biāo)記,在 Core Data Stack 中加入一個(gè)屬性:
var options: NSDictionary?
修改初始化 init 方法:
init(modelName:String, storeName:String, options: NSDictionary? = nil) {
self.modelName = modelName
self.storeName = storeName
self.options = options
}
雖然修改了方法的簽名戒洼,但是option設(shè)置為nil俏橘,意思就是舊的方法簽名仍舊可用,不會(huì)造成程序其他地方需要修改圈浇。
store = coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: nil,
URL: storeURL,
options: self.options)
在創(chuàng)建 Core Data Stack 的類中寥掐,修改初始化的代碼:
lazy var stack : CoreDataStack = CoreDataStack(
modelName:"YourModelName",
storeName:"YourStoreName",
options: [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true])
開關(guān)就在這了
這個(gè) NSMigratePersistentStoresAutomaticallyOption
告訴 CoreData 的 NSPersistentStoreCoordinator 如果存儲(chǔ)層的 Model 和實(shí)際的 Model 不匹配的話就開始執(zhí)行遷移。
后面的 NSInferMappingModelAutomaticallyOption
字面意思翻譯是:Model的自動(dòng)推測映射選項(xiàng)磷蜀,也就是問你要不要打開 輕量遷移召耘。MappingModel 就是遷移的映射助手,它指導(dǎo)著每個(gè)數(shù)據(jù)如何向新的Model中遷移褐隆。
其實(shí)輕量級(jí)遷移的本質(zhì)就是自動(dòng)推測映射模型
污它,所以上面這里Optional就是打開輕量級(jí)遷移的選項(xiàng)。任何遷移都需要 Mapping Model庶弃,只是輕量級(jí)遷移會(huì)由系統(tǒng)根據(jù)推測來自動(dòng)幫你創(chuàng)建一個(gè) Mapping Model衫贬。
下面列舉 輕量遷移 能夠處理的各種情況:
- 刪除實(shí)體、屬性 或者 關(guān)系歇攻。
- 使用
renamingIdentifier
重新命名實(shí)體固惯、屬性 或者關(guān)系。 - 新添加一個(gè) Optional 的屬性缴守。
- 新添加一個(gè) Required 屬性葬毫,但是必須有默認(rèn)值。
- 把一個(gè) Optional 屬性改成帶有默認(rèn)值的 Required 屬性屡穗。
- 把一個(gè) 非Option 的屬性改成 Optional屬性贴捡。
- 改變實(shí)體結(jié)構(gòu)。
- 新添加父實(shí)體村砂,把屬性向父類移動(dòng)或者將父類屬性往子類中移栈暇。
- 把 對(duì)一 關(guān)系改成 對(duì)多 關(guān)系。
- 改變關(guān)系箍镜,從 non-ordered to-many 到 ordered to-many源祈。
3. 手動(dòng)遷移
當(dāng)遷移足夠簡單的時(shí)候,符合上面十種規(guī)范的時(shí)候色迂,可以使用輕量遷移全自動(dòng)完成香缺。但是來了,但是如果你想要更加靈活的遷移歇僧,手動(dòng)遷移第一種是使用 Mapping Model
去實(shí)現(xiàn)遷移图张,功能更強(qiáng)大锋拖,更加靈活。
創(chuàng)建一個(gè)新的 Data Version:
選中 xcdatamodeld
文件然后選擇 Editor
祸轮,選擇 Add Model Version
兽埃,給新版本起一個(gè)名字,可以在原名字后加 v3适袜。
然后在新的 v3 版本中做 Model 的修改柄错,確認(rèn)完成所有修改之后,創(chuàng)建 New File
苦酱,選擇 iOSCoreData
Mapping Model
售貌,然后選擇 Mapping Model Source Data Model
,這個(gè)選 你創(chuàng)建新的 Data Version 的之前的那本版本疫萤,然后下一步選擇Target Model Target Data Model
颂跨,這個(gè)選擇剛剛的那個(gè) v3 版本。
創(chuàng)建的這個(gè)文件的名字按照約定成俗扯饶,前面是Model的名字恒削,加上MappingModel,后面加上舊的Model版本和新的Model版本尾序,ModelNameMappingModel_v2_to_v3
蔓同。
屬性映射
當(dāng)映射模型創(chuàng)建出來的時(shí)候,Xcode 已經(jīng)對(duì)新舊版本進(jìn)行了推測蹲诀,已經(jīng)進(jìn)行了一些屬性的映射,所以可以在這個(gè)Xcode推測的版本上進(jìn)行屬性的映射弃揽。
新創(chuàng)建的屬性映射操作界面包含了兩部分脯爪,上面是屬性映射Attributes Mappings
,下面是關(guān)系映射Relationship Mappings
矿微。
在 Attributes 中配置屬性的映射痕慢,其中 Destination Attribute
目標(biāo)屬性是指新的 Model 中的 Attribute,Value Expression
指的是這項(xiàng)數(shù)據(jù)從哪里來涌矢,在其中可以使用 $source
來作為數(shù)據(jù)源實(shí)例的引用掖举。
在左邊 Enitty Mapping 中顯示的是實(shí)體映射的名稱,選擇一個(gè)實(shí)體映射可以在右邊編輯窗口修改它的名稱娜庇,命名規(guī)范為:源實(shí)體名To新實(shí)體名塔次,比如 NoteToNote,表示從遷移源中的 Note 到新的 Model 中的 Note 的遷移實(shí)體映射名秀。
右邊窗口可以在 Source 中選擇遷移源的 Model励负,選擇后 Xcode 回自動(dòng)根據(jù)屬性名來進(jìn)行一些匹配。
還可以選中左邊的 Enitty Mapping匕得,然后在右邊的 Filter Predicate 中編輯 predicate 條件继榆,比如 name != nil
這一類。
關(guān)系映射
關(guān)系映射我理解是會(huì)比屬性映射比較難理解一些。這里是第一次出現(xiàn)略吨。
你知道集币,跨實(shí)體的遷移時(shí)有會(huì)發(fā)生。舉個(gè)栗子翠忠,比如我們上一個(gè)項(xiàng)目中鞠苟,主實(shí)體是會(huì)議
,會(huì)議有一個(gè)賬單數(shù)組屬性负间,后來要單獨(dú)拉出來做成一個(gè)賬單的 Model偶妖,這個(gè)時(shí)候遷移的時(shí)候要看用戶有多少賬單,我就要遷移中創(chuàng)建多少個(gè)賬單 Model政溃,并且會(huì)議和賬單的關(guān)系是一對(duì)多趾访,全部關(guān)聯(lián)好,這個(gè)實(shí)體之間關(guān)系的遷移就在這里董虱。
在 xcmappingmodel 的下半編輯區(qū)域扼鞋,是屬于 Relationship Mapping 的編輯區(qū)域,可以在這里創(chuàng)建關(guān)系映射愤诱。
右邊的屬性編輯區(qū)域中云头,可以選擇屬性,選擇 Source Fetch 的方式(Auto Generate Value Expression 是自動(dòng)生成)淫半,填寫 keyPath溃槐,還有 MappingName。
FUNCTION(
$manager,
"destinationInstancesForEntityMappingNamed:
sourceInstances:" ,
"NoteToNote", $source)
編輯好的映射的 Value Expression 就像上面這個(gè)一樣科吭。
深入一下這個(gè) $manager
昏滴,它指向一個(gè) NSMigrationManager 對(duì)象,這個(gè)對(duì)象在遷移過程中發(fā)揮作用对人,它處理著遷移的過程谣殊,Migration manager 遷移管理員 管理著源對(duì)象和目標(biāo)對(duì)象的關(guān)聯(lián)。之前有 $source
牺弄,這里又有$manager
姻几,Core Data還提供了別的一些對(duì)象可以在 xcmappingmodel 中使用
NSMigrationManagerKey: $manager
NSMigrationSourceObjectKey: $source
NSMigrationDestinationObjectKey: $destination
NSMigrationEntityMappingKey: $entityMapping
NSMigrationPropertyMappingKey: $propertyMapping
NSMigrationEntityPolicyKey: $entityPolicy
NSEntityMigrationPolicy 自定義遷移的寫在后面。
到這里梳理一下:基于 Mapping Model 的手動(dòng)遷移需要做什么势告?
- 創(chuàng)建新的 Data Version 并作好新數(shù)據(jù)版本的修改工作
- 創(chuàng)建 xcdatamodeld 并配置好 屬性映射 和 關(guān)系映射
- 配置初始化
第三步就是要改一行代碼蛇捌,修改 Core Data Stack:
options: [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: false]
給 NSInferMappingModelAutomaticallyOption 設(shè)置為 False,持久化存儲(chǔ)助手就會(huì)使用 MappingModel 去遷移存儲(chǔ)層咱台。
深入:那這里發(fā)生了什么豁陆?Core Data 發(fā)現(xiàn)不使用輕量級(jí)自動(dòng)遷移,因?yàn)槭?false 嘛吵护,然后它就會(huì)在 bundle 里面去查找它需要的 mapping model盒音,因?yàn)樗喇?dāng)前的Model是從哪一版遷移到哪一版的表鳍,所以它很明確bundle 中的哪一個(gè)文件是它當(dāng)前需要的。