Core Data 迁移与版本管理
大家在學(xué)習(xí)和使用Core Data過(guò)程中,第一次進(jìn)行版本遷移的經(jīng)歷一定是記憶猶新,至少我是這樣的,XD。弄的不好,就會(huì)搞出一些由于遷移過(guò)程中數(shù)據(jù)模型出錯(cuò)導(dǎo)致的Crash。這里總結(jié)了一下Core Data版本遷移過(guò)程中的經(jīng)驗(yàn),希望對(duì)大家有用。
寫(xiě)在前面
關(guān)于Core Data版本遷移,這兩篇文章都進(jìn)行了分析,大家可以參考。
- Core Data Model Versioning and Data Migration Programming Guide
- 自定義 Core Data 遷移
遷移準(zhǔn)備
1) 選中工程中的?xcdaramodeId?文件,Menu->Editor->Add Model Version?
這一步添加完成之后,工程中的*xcdaramodeId* 文件將會(huì)被展開(kāi),并且出現(xiàn)了新增加的Model文件
2) 在Xcode右側(cè)的輔助工具欄中找到 Model Version, 選擇剛剛添加的Model文件,這個(gè)時(shí)候你會(huì)發(fā)現(xiàn)Xcode目錄中,Model文件上的綠色的勾選中了當(dāng)前選擇的Model文件
3) 在新的Model文件中修改最新的Entities等信息,記得也同時(shí)修改NSManagedObject Subclass對(duì)應(yīng)的實(shí)現(xiàn)
4) 修改?NSPersistentStoreCoordinator?部分實(shí)現(xiàn):?
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) // 這里是添加的部分,名如其意,當(dāng)我們需要自動(dòng)版本遷移時(shí),我們需要在addPersistentStoreWithType方法中設(shè)置如下options let options = [NSInferMappingModelAutomaticallyOption: true, NSMigratePersistentStoresAutomaticallyOption: true] var error: NSError? = nil persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error: &error)輕量級(jí)遷移
當(dāng)我們僅僅是對(duì)數(shù)據(jù)模型增加實(shí)體或者可選屬性時(shí),上述步驟完成后運(yùn)行代碼進(jìn)行遷移是奏效的。這個(gè)過(guò)程文檔中叫做?Lightweight Migration?,當(dāng)我們進(jìn)行輕量級(jí)遷移時(shí),?NSPersistentStoreCoordinator?會(huì)為我們自動(dòng)推斷出一個(gè)?Mapping Model?。如果有更加復(fù)雜的改變,我們就需要自己去實(shí)現(xiàn)Mapping Mode。?
添加Mapping Model過(guò)程: New File->CoreData->Mapping Model, 選擇我們需要進(jìn)行Mapping的兩個(gè)Model,最終會(huì)生成一個(gè) *xcmappingmodel* 文件,大家可以打開(kāi)文件,看到里面生成了Model之間的映射。
官方文檔中介紹如下的改變支持輕量級(jí)遷移:
- 為Entity簡(jiǎn)單的添加一個(gè)屬性
- 為Entity移除一個(gè)屬性
- 屬性值由 Optional<-> Non-optional 之間轉(zhuǎn)換
- 為屬性設(shè)置 Default Value
- 重命名Entity或者Attribute
- 增加一個(gè)新的relationship 或者刪除一個(gè)已經(jīng)存在的 relationship
- 重命名relationship
- 改變r(jià)elationship to-one<-> to-many 等
- 增加,刪除Entities
- 增加新的 Parent 或者 Child Entity
- 從Hierarchy中移除Entities
輕量級(jí)遷移不支持合并Entity的層級(jí):比如在舊的Model中兩個(gè)已知的Entities沒(méi)有共享一個(gè)共同的Parent Entity,那么在新的Model中它們也不能夠共享一個(gè)共同的Parent Entity。
在為屬性或者Entity等重命名時(shí),我們需要在Xcode右側(cè)輔助工具欄中找到 Versioning -> RenamingID,設(shè)置Reanaming Identifier為之前對(duì)應(yīng)的名稱。
Mapping Models
如果我們對(duì)數(shù)據(jù)模型的修改不支持輕量級(jí)遷移,我們就需要像上文中所說(shuō)的那樣,自己創(chuàng)建Mapping Model。
打開(kāi)創(chuàng)建好的?xcmappingmodel?文件,我們發(fā)現(xiàn)可以增加或者修改對(duì)應(yīng)的 Entity Mappings, Attibute Mappings 和 Relationship Mappings。?
Core Data提供了如下一組變量允許我們進(jìn)行配置:
NSMigrationManagerKey: $managerNSMigrationSourceObjectKey: $source NSMigrationDestinationObjectKey: $destination NSMigrationEntityMappingKey: $entityMapping NSMigrationPropertyMappingKey: $propertyMapping NSMigrationEntityPolicyKey: $entityPolicy有時(shí)候,我們不僅僅需要修改Entity的屬性或者關(guān)系,可以使用?NSEntityMigrationPolicy?自定義整個(gè)遷移的過(guò)程。繼承?NSEntityMigrationPolicy?實(shí)現(xiàn)遷移過(guò)程,然后選中對(duì)應(yīng)的Entity Mapping,在Xcode右側(cè)輔助工具欄中找到Custom Policy,并設(shè)置為實(shí)現(xiàn)遷移對(duì)應(yīng)的類名。?
NSEntityMigrationPolicy
NSEntityMigrationPolicy目前提供了7個(gè)方法可供實(shí)現(xiàn),它們的調(diào)用順序如下:
1) 當(dāng)遷移將要開(kāi)始時(shí),會(huì)調(diào)用
func beginEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool2) 在舊數(shù)據(jù)上構(gòu)建新的實(shí)例時(shí)調(diào)用
func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool結(jié)束時(shí)調(diào)用
func endInstanceCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool3) 構(gòu)建新的RelationShips調(diào)用
func createRelationshipsForDestinationInstance(dInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool`結(jié)束時(shí)調(diào)用
func endRelationshipCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool4) 驗(yàn)證,保存數(shù)據(jù)調(diào)用
func performCustomValidationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool5) 遷移結(jié)束時(shí)調(diào)用
func endEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool遷移過(guò)程
這里分享的是自己項(xiàng)目中數(shù)據(jù)自定義遷移的整個(gè)過(guò)程,并附上部分代碼實(shí)現(xiàn)邏輯。項(xiàng)目中采用的是漸進(jìn)式遷移,漸進(jìn)式遷移的概念在?自定義 Core Data 遷移?一文中有介紹:?
// 這段文字取自 <<自定義 Core Data 遷移>> 一文想像一下你剛剛部署一個(gè)包含版本 3 的數(shù)據(jù)模型的更新。你的某個(gè)用戶已經(jīng)有一段時(shí)間沒(méi)有更新你的應(yīng)用了,這個(gè)用戶還在版本 1 的數(shù)據(jù)模型上。那么現(xiàn)在你就需要一個(gè)從版本 1 到版本 3 的映射模型。同時(shí)你也需要版本 2 到版本 3 的映射模型。當(dāng)你添加了版本 4 的數(shù)據(jù)模型后,那你就需要?jiǎng)?chuàng)建三個(gè)新的映射模型。顯然這樣做的擴(kuò)展性很差,那就來(lái)試試漸進(jìn)式遷移吧。與其為每個(gè)之前的數(shù)據(jù)模型到最新的模型間都建立映射模型,還不如在每?jī)蓚€(gè)連續(xù)的數(shù)據(jù)模型之間創(chuàng)建映射模型。以前面的例子來(lái)說(shuō),版本 1 和版本 2 之間需要一個(gè)映射模型,版本 2 和版本 3 之間需要一個(gè)映射模型。這樣就可以從版本 1 遷移到版本 2 再遷移到版本 3。顯然,使用這種遷移的方式時(shí),若用戶在較老的版本上遷移過(guò)程就會(huì)比較慢,但它能節(jié)省開(kāi)發(fā)時(shí)間并保證健壯性,因?yàn)槟阒恍枰_保從之前一個(gè)模型到新模型的遷移工作正常即可,而更前面的映射模型都已經(jīng)經(jīng)過(guò)了測(cè)試。1) 判斷本地SQLite數(shù)據(jù)庫(kù)文件是否存在,不存在直接退出整個(gè)遷移。
2) 檢測(cè)當(dāng)前本地?cái)?shù)據(jù)庫(kù)和數(shù)據(jù)模型是否一致,如果一致就退出遷移。
let storeURL = NSURL(fileURLWithPath: "SQLite file path") let managedObjectModel: NSManagedObjectModel = Your current managed object modellet sourceMetadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(NSSQLiteStoreType, URL:storeURL!, error: nil) Bool needMigration = managedObjectModel.isConfiguration(nil, compatibleWithStoreMetadata: sourceMetadata)3) 取得當(dāng)前數(shù)據(jù)庫(kù)存儲(chǔ)時(shí)用的數(shù)據(jù)模型,如果獲取不到或者獲取失敗,退出遷移。
if let sourceModel = NSManagedObjectModel.mergedModelFromBundles(nil, forStoreMetadata: sourceMetadata!) {println("\(sourceModel)") } else {return }4) 取得當(dāng)前工程中所有數(shù)據(jù)模型對(duì)應(yīng)的managedObjectModel用于遷移,如果獲取的結(jié)果少于兩個(gè)就退出遷移。
5) 從所有的managedObjectModel中遍歷出最終使用的Model和當(dāng)前數(shù)據(jù)庫(kù)采用的Model之間的所有Model,按照version順序構(gòu)建一個(gè)新的 Model list,確保第一個(gè)是sourceModel,最后一個(gè)是當(dāng)前需要使用的managedObjectModel。
6) 對(duì)生成的這個(gè)Model list進(jìn)行循環(huán),開(kāi)始漸進(jìn)式遷移。
7) 構(gòu)建Model list中相鄰兩個(gè)Model之間的NSMappingModel實(shí)例,用做遷移。
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一個(gè) mappingModel = NSMappingModel.inferredMappingModelForSourceModel(modelA, destinationModel: modelB, error: nil) //如果最終取不到Mapping Model 就退出遷移 //如果得到了Mapping Model,就可以開(kāi)始進(jìn)行遷移 }8) 終于可以開(kāi)始進(jìn)行遷移了,XD
9) 創(chuàng)建一個(gè)新的文件路徑用來(lái)存儲(chǔ)遷移過(guò)程中的數(shù)據(jù)文件
10) 使用上文中的 modelA 和 modelB 構(gòu)建一個(gè)?NSMigrationManager?實(shí)例,使用?
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方法進(jìn)行遷移,其中?toDestinationURL?參數(shù)是我們?cè)诓襟E9中創(chuàng)建的路徑。如果migrate失敗,就退出整個(gè)遷移過(guò)程。?
11) 數(shù)據(jù)替換 1.把原始的source文件移動(dòng)到新的backup文件夾中 2.使用步驟9中文件下生成的遷移之后的數(shù)據(jù)文件移動(dòng)到原始的數(shù)據(jù)的路徑下 3.刪除backup
12) 回到步驟7,進(jìn)行下一次迭代遷移,直到結(jié)束
遷移調(diào)試
我們可以在Xcode中設(shè)置啟動(dòng)參數(shù)?-com.apple.coredata.ubiquity.logLevel 3?或者?-com.apple.CoreData.SQLDebug 1?, 這樣在程序運(yùn)行時(shí),控制臺(tái)將會(huì)打印更多Core Data使用中的信息,包括調(diào)用的SQL語(yǔ)句。?
轉(zhuǎn)載于:https://www.cnblogs.com/lihaibo-Leao/p/5536930.html
總結(jié)
以上是生活随笔為你收集整理的Core Data 迁移与版本管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: TOPCODER SAM 686 div
- 下一篇: 有关接口 笔记 懒人版