大家在學習和使用Core Data過程中,第一次進行版本遷移的經歷一定是記憶猶新,至少我是這樣的,XD。弄的不好,就會搞出一些由於遷移過程中資料模型出錯導致的Crash。這裡總結了一下Core Data版本遷移過程中的經驗,希望對大家有用。
寫在前面
關於Core Data版本遷移,這兩篇文章都進行了分析,大家可以參考。
遷移準備
1) 選中工程中的 xcdaramodeId 檔案,Menu->Editor->Add Model Version
這一步新增完成之後,工程中的*xcdaramodeId* 檔案將會被展開,並且出現了新增加的Model檔案
2) 在Xcode右側的輔助工具欄中找到 Model Version, 選擇剛剛新增的Model檔案,這個時候你會發現Xcode目錄中,Model檔案上的綠色的勾選中了當前選擇的Model檔案
3) 在新的Model檔案中修改最新的Entities等資訊,記得也同時修改NSManagedObject Subclass對應的實現
4) 修改 NSPersistentStoreCoordinator
部分實現:
1 2 3 4 5 6 7 8 9 10 11 12 |
let modelFilename = "the model file name in your project" let modelPath = NSBundle.mainBundle().pathForResource(modelFIlename, ofType: "momd") let managedObjectModel = NSManagedObjectModel(contentsOfURL: NSURL.fileURLWithPath(modelPath) let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) // 這裡是新增的部分,名如其意,當我們需要自動版本遷移時,我們需要在addPersistentStoreWithType方法中設定如下options let options = [NSInferMappingModelAutomaticallyOption: true, NSMigratePersistentStoresAutomaticallyOption: true] var error: NSError? = nil persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error: |
輕量級遷移
當我們僅僅是對資料模型增加實體或者可選屬性時,上述步驟完成後執行程式碼進行遷移是奏效的。這個過程文件中叫做 Lightweight Migration,當我們進行輕量級遷移時,NSPersistentStoreCoordinator 會為我們自動推斷出一個Mapping Model
。如果有更加複雜的改變,我們就需要自己去實現Mapping Mode。
新增Mapping Model過程: New File->CoreData->Mapping Model, 選擇我們需要進行Mapping的兩個Model,最終會生成一個 *xcmappingmodel* 檔案,大家可以開啟檔案,看到裡面生成了Model之間的對映。
官方文件中介紹如下的改變支援輕量級遷移:
- 為Entity簡單的新增一個屬性
- 為Entity移除一個屬性
- 屬性值由 Optional Non-optional 之間轉換
- 為屬性設定 Default Value
- 重新命名Entity或者Attribute
- 增加一個新的relationship 或者刪除一個已經存在的 relationship
- 重新命名relationship
- 改變relationship to-one to-many 等
- 增加,刪除Entities
- 增加新的 Parent 或者 Child Entity
- 從Hierarchy中移除Entities
輕量級遷移不支援合併Entity的層級:比如在舊的Model中兩個已知的Entities沒有共享一個共同的Parent Entity,那麼在新的Model中它們也不能夠共享一個共同的Parent Entity。
在為屬性或者Entity等重新命名時,我們需要在Xcode右側輔助工具欄中找到 Versioning -> RenamingID,設定Reanaming Identifier為之前對應的名稱。
Mapping Models
如果我們對資料模型的修改不支援輕量級遷移,我們就需要像上文中所說的那樣,自己建立Mapping Model。
開啟建立好的xcmappingmodel檔案,我們發現可以增加或者修改對應的 Entity Mappings, Attibute Mappings 和 Relationship Mappings。
Core Data提供瞭如下一組變數允許我們進行配置:
1 2 3 4 5 6 7 8 9 10 11 |
NSMigrationManagerKey: $manager NSMigrationSourceObjectKey: $source NSMigrationDestinationObjectKey: $destination NSMigrationEntityMappingKey: $entityMapping NSMigrationPropertyMappingKey: $propertyMapping NSMigrationEntityPolicyKey: $entityPolicy |
有時候,我們不僅僅需要修改Entity的屬性或者關係,可以使用NSEntityMigrationPolicy
自定義整個遷移的過程。繼承NSEntityMigrationPolicy
實現遷移過程,然後選中對應的Entity Mapping,在Xcode右側輔助工具欄中找到Custom Policy,並設定為實現遷移對應的類名。
NSEntityMigrationPolicy
NSEntityMigrationPolicy目前提供了7個方法可供實現,它們的呼叫順序如下:
1) 當遷移將要開始時,會呼叫
1 |
func beginEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool |
2) 在舊資料上構建新的例項時呼叫
1 |
func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool |
結束時呼叫
1 |
func endInstanceCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool |
3) 構建新的RelationShips呼叫
1 |
func createRelationshipsForDestinationInstance(dInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool` |
結束時呼叫
1 |
func endRelationshipCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool |
4) 驗證,儲存資料呼叫
1 |
func performCustomValidationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool |
5) 遷移結束時呼叫
1 |
func endEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool |
遷移過程
這裡分享的是自己專案中資料自定義遷移的整個過程,並附上部分程式碼實現邏輯。專案中採用的是漸進式遷移,漸進式遷移的概念在自定義 Core Data 遷移一文中有介紹:
1 2 3 4 5 |
// 這段文字取自 > 一文 想像一下你剛剛部署一個包含版本 3 的資料模型的更新。你的某個使用者已經有一段時間沒有更新你的應用了,這個使用者還在版本 1 的資料模型上。那麼現在你就需要一個從版本 1 到版本 3 的對映模型。同時你也需要版本 2 到版本 3 的對映模型。當你新增了版本 4 的資料模型後,那你就需要建立三個新的對映模型。顯然這樣做的擴充套件性很差,那就來試試漸進式遷移吧。 與其為每個之前的資料模型到最新的模型間都建立對映模型,還不如在每兩個連續的資料模型之間建立對映模型。以前面的例子來說,版本 1 和版本 2 之間需要一個對映模型,版本 2 和版本 3 之間需要一個對映模型。這樣就可以從版本 1 遷移到版本 2 再遷移到版本 3。顯然,使用這種遷移的方式時,若使用者在較老的版本上遷移過程就會比較慢,但它能節省開發時間並保證健壯性,因為你只需要確保從之前一個模型到新模型的遷移工作正常即可,而更前面的對映模型都已經經過了測試。 |
1) 判斷本地SQLite資料庫檔案是否存在,不存在直接退出整個遷移。
2) 檢測當前本地資料庫和資料模型是否一致,如果一致就退出遷移。
1 2 3 4 5 6 |
let storeURL = NSURL(fileURLWithPath: "SQLite file path") let managedObjectModel: NSManagedObjectModel = Your current managed object model let sourceMetadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(NSSQLiteStoreType, URL:storeURL!, error: nil) Bool needMigration = managedObjectModel.isConfiguration(nil, compatibleWithStoreMetadata: sourceMetadata) |
3) 取得當前資料庫儲存時用的資料模型,如果獲取不到或者獲取失敗,退出遷移。
1 2 3 4 5 |
if let sourceModel = NSManagedObjectModel.mergedModelFromBundles(nil, forStoreMetadata: sourceMetadata!) { println("(sourceModel)") } else { return } |
4) 取得當前工程中所有資料模型對應的managedObjectModel用於遷移,如果獲取的結果少於兩個就退出遷移。
5) 從所有的managedObjectModel中遍歷出最終使用的Model和當前資料庫採用的Model之間的所有Model,按照version順序構建一個新的 Model list,確保第一個是sourceModel,最後一個是當前需要使用的managedObjectModel。
6) 對生成的這個Model list進行迴圈,開始漸進式遷移。
7) 構建Model list中相鄰兩個Model之間的NSMappingModel例項,用做遷移。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
for var index = 0; index < modelList.count - 1; index++ { let modelA = modelList[index] let modelB = modelList[index + 1] //檢查是否有自定義的Mapping model存在 var mappingModel : NSMappingModel? = NSMappingModel(fromBundles: nil, forSourceModel: modelA, destinationModel: modelB) //如果不存在,嘗試infer一個 mappingModel = NSMappingModel.inferredMappingModelForSourceModel(modelA, destinationModel: modelB, error: nil) //如果最終取不到Mapping Model 就退出遷移 //如果得到了Mapping Model,就可以開始進行遷移 } |
8) 終於可以開始進行遷移了,XD
9) 建立一個新的檔案路徑用來儲存遷移過程中的資料檔案
10) 使用上文中的 modelA 和 modelB 構建一個 NSMigrationManager 例項,使用
1 |
func migrateStoreFromURL(sourceURL: NSURL, type sStoreType: String, options sOptions: [NSObject : AnyObject]?, withMappingModel mappings: NSMappingModel?, toDestinationURL dURL: NSURL, destinationType dStoreType: String, destinationOptions dOptions: [NSObject : AnyObject]?, error: NSErrorPointer) -> Bool |
方法進行遷移,其中 toDestinationURL
引數是我們在步驟9中建立的路徑。如果migrate失敗,就退出整個遷移過程。
11) 資料替換 1.把原始的source檔案移動到新的backup資料夾中 2.使用步驟9中檔案下生成的遷移之後的資料檔案移動到原始的資料的路徑下 3.刪除backup
12) 回到步驟7,進行下一次迭代遷移,直到結束
遷移除錯
我們可以在Xcode中設定啟動引數 -com.apple.coredata.ubiquity.logLevel 3
或者 -com.apple.CoreData.SQLDebug 1
, 這樣在程式執行時,控制檯將會列印更多Core Data使用中的資訊,包括呼叫的SQL語句。
- 本文是關於iOS Core Data 的學習筆記。有不對的地方,歡迎大家指正。
- 本文對應參考文獻為 Core Data Model Versioning and Data Migration Programming Guide 和 自定義 Core Data 遷移