前言
Core Data是iOS上一個(gè)效率比較高的數(shù)據(jù)庫(kù)框架,(但是Core Data并不是一種數(shù)據(jù)庫(kù)捌省,它底層還是利用Sqlite3來(lái)存儲(chǔ)數(shù)據(jù)的)苫纤,它可以把數(shù)據(jù)當(dāng)成對(duì)象來(lái)操作,而且開(kāi)發(fā)者并不需要在乎數(shù)據(jù)在磁盤(pán)上面的存儲(chǔ)方式所禀。它會(huì)把位于NSManagedObject Context里面的托管對(duì)象NSManagedObject類(lèi)的實(shí)例或者某個(gè)NSManagedObject子類(lèi)的實(shí)例,通過(guò)NSManagedObjectModel托管對(duì)象模型放钦,把托管對(duì)象保存到持久化存儲(chǔ)協(xié)調(diào)器NSPersistentStoreCoordinator持有的一個(gè)或者多個(gè)持久化存儲(chǔ)區(qū)中NSPersistentStore中色徘。使用Core Data進(jìn)行查詢(xún)的語(yǔ)句都是經(jīng)過(guò)Apple特別優(yōu)化過(guò)的,所以都是效率很高的查詢(xún)操禀。
當(dāng)你進(jìn)行簡(jiǎn)單的設(shè)定褂策,比如說(shuō)設(shè)定某個(gè)實(shí)體的默認(rèn)值,設(shè)定級(jí)聯(lián)刪除的操作颓屑,設(shè)定數(shù)據(jù)的驗(yàn)證規(guī)則斤寂,使用數(shù)據(jù)的請(qǐng)求模板,這些修改Core Data都會(huì)自己完成揪惦,不用自己進(jìn)行數(shù)據(jù)遷移遍搞。那那些操作需要我們進(jìn)行數(shù)據(jù)遷移呢?凡是會(huì)引起NSManagedObjectModel托管對(duì)象模型變化的器腋,都最好進(jìn)行數(shù)據(jù)遷移溪猿,防止用戶(hù)升級(jí)應(yīng)用之后就閃退。會(huì)引起NSManagedObjectModel托管對(duì)象模型變化的有以下幾個(gè)操作纫塌,新增了一張表诊县,新增了一張表里面的一個(gè)實(shí)體,新增一個(gè)實(shí)體的一個(gè)屬性措左,把一個(gè)實(shí)體的某個(gè)屬性遷移到另外一個(gè)實(shí)體的某個(gè)屬性里面…………大家應(yīng)該現(xiàn)在都知道哪些操作需要進(jìn)行數(shù)據(jù)遷移了吧依痊。
小技巧:
進(jìn)入正題之前,我先說(shuō)3個(gè)調(diào)試Core Data里面調(diào)試可能你會(huì)需要的操作怎披。
1.一般打開(kāi)app沙盒里面的會(huì)有三種類(lèi)型的文件胸嘁,sqlite,sqlite-shm,sqlite-wal,后面2者是iOS7之后系統(tǒng)會(huì)默認(rèn)開(kāi)啟一個(gè)新的“數(shù)據(jù)庫(kù)日志記錄模式”(database journaling mode)生成的凉逛,sqlite-shm是共享內(nèi)存(Shared Memory)文件缴渊,該文件里面會(huì)包含一份sqlite-wal文件的索引,系統(tǒng)會(huì)自動(dòng)生成shm文件鱼炒,所以刪除它衔沼,下次運(yùn)行還會(huì)生成蝌借。sqlite-wal是預(yù)寫(xiě)式日志(Write-Ahead Log)文件,這個(gè)文件里面會(huì)包含尚未提交的數(shù)據(jù)庫(kù)事務(wù)指蚁,所以看見(jiàn)有這個(gè)文件了菩佑,就代表數(shù)據(jù)庫(kù)里面還有還沒(méi)有處理完的事務(wù)需要提交,所以說(shuō)如果有sqlite-wal文件凝化,再去打開(kāi)sqlite文件稍坯,很可能最近一次數(shù)據(jù)庫(kù)操作還沒(méi)有執(zhí)行。
所以在調(diào)試的時(shí)候搓劫,我們需要即時(shí)的觀察數(shù)據(jù)庫(kù)的變化瞧哟,我們就可以先禁用這個(gè)日志記錄模式,只需要在建立持久化存儲(chǔ)區(qū)的時(shí)候存入一個(gè)參數(shù)即可枪向。具體代碼如下
NSDictionary *options =
@{
NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"}
};
NSError *error = nil;
_store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeURL]
options:options error:&error];
2.Mac上打開(kāi)數(shù)據(jù)庫(kù)的方式很多勤揩,我推薦3個(gè),一個(gè)是Firefox里面直接有sqlite的插件秘蛔,免費(fèi)的陨亡,可以直接安裝,也很方便深员。當(dāng)然也有不用Firefox的朋友负蠕,就像我是Chrome重度使用者,那就推薦2個(gè)免費(fèi)的小的app倦畅,一個(gè)是sqlitebrowser遮糖,一個(gè)是sqlite manager,這2個(gè)都比較輕量級(jí)叠赐,都比較好用止吁。
3.如果你想看看Core Data到底底層是如何優(yōu)化你的查詢(xún)語(yǔ)句的,這里有一個(gè)方法可以看到燎悍。
先點(diǎn)擊Product ->Scheme ->Edit Scheme
然后再切換到Arguments分頁(yè)中,在Arguments Passed On Launch里面加入 “- com.apple.CoreData.SQLDebug 3”,重新運(yùn)行app敬惦,下面就會(huì)顯示Core Data優(yōu)化過(guò)的Sql語(yǔ)句了。
好了谈山,調(diào)試信息應(yīng)該都可以完美顯示了俄删,可以開(kāi)始愉快的進(jìn)入正文了!
一.Core Data自帶的輕量級(jí)的數(shù)據(jù)遷移
這種遷移可別小看它奏路,在你新建一張表的時(shí)候還必須加上它才行畴椰,否則會(huì)出現(xiàn)如下的錯(cuò)誤,
**Failed to add store. Error: Error Domain=NSCocoaErrorDomain Code=134100 "(null)" UserInfo={metadata={**
** NSPersistenceFrameworkVersion = 641;**
** NSStoreModelVersionHashes = {**
** Item = <64288772 72e62096 a8a4914f 83db23c9 13718f81 4417e297 293d0267 79b04acb>;**
** Measurement = <35717f0e 32cae0d4 57325758 58ed0d11 c16563f2 567dac35 de63d5d8 47849cf7>;**
** };**
** NSStoreModelVersionHashesVersion = 3;**
** NSStoreModelVersionIdentifiers = (**
** ""**
** );**
** NSStoreType = SQLite;**
** NSStoreUUID = "9A16746E-0C61-421B-B936-412F0C904FDF";**
** "_NSAutoVacuumLevel" = 2;**
**}, reason=The model used to open the store is incompatible with the one used to create the store}**
錯(cuò)誤原因?qū)懙谋容^清楚了鸽粉,reason=The model used to open the store is incompatible with the one used to create the store斜脂,這個(gè)是因?yàn)槲倚陆艘粡埍恚俏覜](méi)有打開(kāi)輕量級(jí)的遷移Option触机。這里會(huì)有人會(huì)問(wèn)了帚戳,我新建表從來(lái)沒(méi)有出現(xiàn)這個(gè)錯(cuò)誤扮杌颉?那是因?yàn)槟銈冇玫牡谌娇蚣芫鸵呀?jīng)寫(xiě)好了改Option了片任。(場(chǎng)外人:這年頭誰(shuí)還自己從0開(kāi)始寫(xiě)Core Data啊偏友,肯定都用第三方框架啊)那這里我就當(dāng)講解原理了哈。如果是自己從0開(kāi)始寫(xiě)的Core Data的話(huà)对供,這里是應(yīng)該會(huì)報(bào)錯(cuò)了位他,解決辦法當(dāng)然是加上代碼,利用Core Data的輕量級(jí)遷移产场,來(lái)防止這種找不到存儲(chǔ)區(qū)的閃退問(wèn)題
NSDictionary *options =
@{
NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"},
NSMigratePersistentStoresAutomaticallyOption :@YES,
NSInferMappingModelAutomaticallyOption:@YES
};
NSError *error = nil;
_store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeURL]
options:options error:&error];
這里說(shuō)一下新增加的2個(gè)參數(shù)的意義:
NSMigratePersistentStoresAutomaticallyOption = YES鹅髓,那么Core Data會(huì)試著把之前低版本的出現(xiàn)不兼容的持久化存儲(chǔ)區(qū)遷移到新的模型中,這里的例子里京景,Core Data就能識(shí)別出是新表窿冯,就會(huì)新建出新表的存儲(chǔ)區(qū)來(lái),上面就不會(huì)報(bào)上面的error了醋粟。
NSInferMappingModelAutomaticallyOption = YES,這個(gè)參數(shù)的意義是Core Data會(huì)根據(jù)自己認(rèn)為最合理的方式去嘗試MappingModel靡菇,從源模型實(shí)體的某個(gè)屬性重归,映射到目標(biāo)模型實(shí)體的某個(gè)屬性米愿。
接著我們來(lái)看看MagicRecord源碼是怎么寫(xiě)的,所以大家才能執(zhí)行一些操作不會(huì)出現(xiàn)我上面說(shuō)的閃退的問(wèn)題
+ (NSDictionary *) MR_autoMigrationOptions;
{
// Adding the journalling mode recommended by apple
NSMutableDictionary *sqliteOptions = [NSMutableDictionary dictionary];
[sqliteOptions setObject:@"WAL" forKey:@"journal_mode"];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
sqliteOptions, NSSQLitePragmasOption,
nil];
return options;
}
上面這一段就是MagicRecord源碼里面替大家加的Core Data輕量級(jí)的數(shù)據(jù)遷移的保護(hù)了鼻吮,所以大家不寫(xiě)那2個(gè)參數(shù)育苟,一樣不會(huì)報(bào)錯(cuò)。(題外話(huà):MagicRecord默認(rèn)這里是開(kāi)啟了WAL日志記錄模式了) 此處如果大家注銷(xiāo)掉那兩個(gè)參數(shù)椎木,或者把參數(shù)的值設(shè)置為NO违柏,再運(yùn)行一次佛吓,新建一張表多矮,就會(huì)出現(xiàn)我上面提到的錯(cuò)誤了种柑。大家可以實(shí)踐實(shí)踐件舵,畢竟實(shí)踐出真知嘛铝侵。
只要打開(kāi)上面2個(gè)參數(shù)娇斑,Core Data就會(huì)執(zhí)行自己的輕量級(jí)遷移了燎猛,當(dāng)然龙宏,在實(shí)體屬性遷移時(shí)候玛界,用該方式不靠譜万矾,之前我覺(jué)得它肯定能推斷出來(lái),結(jié)果后來(lái)還是更新后直接閃退報(bào)錯(cuò)了慎框,可能是因?yàn)楸斫Y(jié)構(gòu)太復(fù)雜良狈,超過(guò)了它簡(jiǎn)單推斷的能力范圍了,所以我建議笨枯,在進(jìn)行復(fù)雜的實(shí)體屬性遷移到另一個(gè)屬性遷移的時(shí)候薪丁,不要太相信這種方式遇西,還是最好自己Mapping一次。當(dāng)然窥突,你要是新建一張表的時(shí)候努溃,這2個(gè)參數(shù)是必須要加上的!W栉省梧税!
二.Core Data手動(dòng)創(chuàng)建Mapping文件進(jìn)行遷移
這種方式比前一種方式要更加精細(xì)一些,Mapping文件會(huì)指定哪個(gè)實(shí)體的某個(gè)屬性遷移到哪個(gè)實(shí)體的某個(gè)屬性称近,這比第一種交給Core Data自己去推斷要靠譜一些第队,這種方法直接指定映射!
先說(shuō)一下刨秆,如果復(fù)雜的遷移凳谦,不加入這個(gè)Mapping文件會(huì)出現(xiàn)什么樣的錯(cuò)誤
**Failed to add store. Error: Error Domain=NSCocoaErrorDomain Code=134140 "(null)" UserInfo={destinationModel=(<NSManagedObjectModel: 0x7f82d4935280>) isEditable 0, entities {**
** Amount = "(<NSEntityDescription: 0x7f82d4931960>) name Amount, managedObjectClassName NSManagedObject, renamingIdentifier Amount, isAbstract 0, superentity name (null), properties {\n qwe = \"(<NSAttributeDescription: 0x7f82d4930f40>), name qwe, isOptional 1, isTransient 0, entity Amount, renamingIdentifier qwe, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 700 , attributeValueClassName NSString, defaultValue (null)\";\n}, subentities {\n}, userInfo {\n}, versionHashModifier (null), uniquenessConstraints (\n)";**
** Item = "(<NSEntityDescription: 0x7f82d4931a10>) name Item, managedObjectClassName Item, renamingIdentifier Item, isAbstract 0, superentity name (null), properties {\n collected = \"(<NSAttributeDescription: 0x7f82d4930fd0>), name collected, isOptional 1, isTransient 0, entity Item, renamingIdentifier collected, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 800 , attributeValueClassName NSNumber, defaultValue 0\";\n listed = \"(<NSAttributeDescription: 0x7f82d4931060>), name listed, isOptional 1, isTransient 0, entity Item, renamingIdentifier listed, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 800 , attributeValueClassName NSNumber, defaultValue 1\";\n name = \"(<NSAttributeDescription: 0x7f82d49310f0>), name name, isOptional 1, isTransient 0, entity Item, renamingIdentifier name, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 700 , attributeValueClassName NSString, defaultValue New Item\";\n photoData = \"(<NSAttributeDescription: 0x7f82d4931180>), name photoData, isOptional 1, isTransient 0, entity Item, renamingIdentifier photoData, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 1000 , attributeValueClassName NSData, defaultValue (null)\";\n quantity = \"(<NSAttributeDescription: 0x7f82d4931210>), name quantity, isOptional 1, isTransient 0, entity Item, renamingIdentifier quantity, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 600 , attributeValueClassName NSNumber, defaultValue 1\";\n}, subentities {\n}, userInfo {\n}, versionHashModifier (null), uniquenessConstraints (\n)";**
**}, fetch request templates {**
** Test = "<NSFetchRequest: 0x7f82d49316c0> (entity: Item; predicate: (name CONTAINS \"e\"); sortDescriptors: ((null)); type: NSManagedObjectResultType; )";**
**}, sourceModel=(<NSManagedObjectModel: 0x7f82d488e930>) isEditable 1, entities {**
** Amount = "(<NSEntityDescription: 0x7f82d488f880>) name Amount, managedObjectClassName NSManagedObject, renamingIdentifier Amount, isAbstract 0, superentity name (null), properties {\n abc = \"(<NSAttributeDescription: 0x7f82d488f9d0>), name abc, isOptional 1, isTransient 0, entity Amount, renamingIdentifier abc, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 700 , attributeValueClassName NSString, defaultValue (null)\";\n}, subentities {\n}, userInfo {\n}, versionHashModifier (null), uniquenessConstraints (\n)";**
** Item = "(<NSEntityDescription: 0x7f82d488fbe0>) name Item, managedObjectClassName NSManagedObject, renamingIdentifier Item, isAbstract 0, superentity name (null), properties {\n collected = \"(<NSAttributeDescription: 0x7f82d48901c0>), name collected, isOptional 1, isTransient 0, entity Item, renamingIdentifier collected, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 800 , attributeValueClassName NSNumber, defaultValue 0\";\n listed = \"(<NSAttributeDescription: 0x7f82d488fd20>), name listed, isOptional 1, isTransient 0, entity Item, renamingIdentifier listed, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 800 , attributeValueClassName NSNumber, defaultValue 1\";\n name = \"(<NSAttributeDescription: 0x7f82d488fdb0>), name name, isOptional 1, isTransient 0, entity Item, renamingIdentifier name, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 700 , attributeValueClassName NSString, defaultValue New Item\";\n photoData = \"(<NSAttributeDescription: 0x7f82d488fad0>), name photoData, isOptional 1, isTransient 0, entity Item, renamingIdentifier photoData, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 1000 , attributeValueClassName NSData, defaultValue (null)\";\n quantity = \"(<NSAttributeDescription: 0x7f82d488fc90>), name quantity, isOptional 1, isTransient 0, entity Item, renamingIdentifier quantity, validation predicates (\\n), warnings (\\n), versionHashModifier (null)\\n userInfo {\\n}, attributeType 600 , attributeValueClassName NSNumber, defaultValue 1\";\n}, subentities {\n}, userInfo {\n}, versionHashModifier (null), uniquenessConstraints (\n)";**
**}, fetch request templates {**
** Test = "<NSFetchRequest: 0x7f82d488fa60> (entity: Item; predicate: (name CONTAINS \"e\"); sortDescriptors: ((null)); type: NSManagedObjectResultType; )";**
**}, reason=Can't find mapping model for migration}**
直接看最后一行錯(cuò)誤的原因Can't find mapping model for migration,這直接說(shuō)出了錯(cuò)誤的原因衡未,那么接下來(lái)我們就創(chuàng)建一個(gè)Mapping Model文件尸执。
在你xcdatamodeld相同的文件夾目錄下,“New File” ->"Core Data"->"Mapping Model"
選擇需要Mapping的源數(shù)據(jù)庫(kù)
再選擇目標(biāo)數(shù)據(jù)庫(kù)
接著命名一下Mapping Model文件的名字
這里說(shuō)明一下缓醋,名字最好能一眼看上去就能區(qū)分出是哪個(gè)數(shù)據(jù)庫(kù)的版本升級(jí)上來(lái)的如失,這里我寫(xiě)的就是ModelV4ToV5,這樣一看就知道是V4到V5的升級(jí)送粱。
這里說(shuō)明一下Mapping文件的重要性褪贵,首先,每個(gè)版本的數(shù)據(jù)庫(kù)之間都最好能加上一個(gè)Mapping文件抗俄,這樣從低版本的數(shù)據(jù)庫(kù)升級(jí)上來(lái)脆丁,可以保證每個(gè)版本都不會(huì)出錯(cuò),都不會(huì)導(dǎo)致用戶(hù)升級(jí)之后就出現(xiàn)閃退的問(wèn)題动雹。
比如上圖槽卫,每個(gè)數(shù)據(jù)庫(kù)之間都會(huì)對(duì)應(yīng)一個(gè)Mapping文件,V0ToV1,V1ToV2,V2ToV3,V3ToV4,V4ToV5,每個(gè)Mapping都必須要胰蝠。
試想歼培,如果用戶(hù)實(shí)在V3的老版本上,由于appstore的更新規(guī)則姊氓,每次更新都直接更新到最新丐怯,那么用戶(hù)更新之后就會(huì)直接到V5,如果缺少了中間的V3ToV4,V4ToV5翔横,中的任意一個(gè)读跷,那么V3的用戶(hù)都無(wú)法升級(jí)到V5上來(lái),都會(huì)閃退禾唁。所以這里就看出了每個(gè)版本之間都要加上Mapping文件的重要性了效览。這樣任意低版本的用戶(hù)无切,任何時(shí)刻都可以通過(guò)Mapping文件,隨意升級(jí)到最新版丐枉,而且不會(huì)閃退了哆键!
接下來(lái)再說(shuō)說(shuō)Mapping文件打開(kāi)是些什么東西。
Mapping文件打開(kāi)對(duì)應(yīng)的就是Source源實(shí)體屬性瘦锹,遷移到Target目標(biāo)實(shí)體屬性的映射籍嘹,上面是屬性,下面是關(guān)系的映射弯院。$source就是代表的源實(shí)體
寫(xiě)到這里辱士,就可以很清楚的區(qū)分一下到目前為止,Core Data輕量級(jí)遷移和手動(dòng)創(chuàng)建Mapping進(jìn)行遷移听绳,這2種方法的異同點(diǎn)了颂碘。我簡(jiǎn)單總結(jié)一下:
1.Core Data輕量級(jí)遷移是適用于添加新表,添加新的實(shí)體椅挣,添加新的實(shí)體屬性头岔,等簡(jiǎn)單的,系統(tǒng)能自己推斷出來(lái)的遷移方式鼠证。
2.手動(dòng)創(chuàng)建Mapping適用于更加復(fù)雜的數(shù)據(jù)遷移
舉個(gè)例子吧峡竣,假設(shè)我最初有一張很抽象的表,叫Object表名惩,用來(lái)存儲(chǔ)東西的一些屬性澎胡,里面假設(shè)有name孕荠,width娩鹉,height。突然我有一天有新需求了稚伍,需要在Object表里面新增幾個(gè)字段弯予,比如說(shuō)colour,weight等个曙,由于這個(gè)都是簡(jiǎn)單的新增锈嫩,不涉及到數(shù)據(jù)的轉(zhuǎn)移,這時(shí)候用輕量級(jí)遷移就可以了垦搬。
不過(guò)突然有一個(gè)程序又有新需求了呼寸,需要增加2張表,一個(gè)是Human表猴贰,一個(gè)是Animal表对雪,需要把當(dāng)初抽象定義的Object表更加具體化。這時(shí)就需要把Object里面的人都抽出來(lái)米绕,放到新建的Human表里瑟捣,動(dòng)物也都抽出來(lái)放到新建的Animal表里馋艺。由于新建的2張表都會(huì)有name屬性,如果這個(gè)時(shí)候進(jìn)行輕量級(jí)的遷移迈套,系統(tǒng)可能推斷不出到底哪些name要到Human表里捐祠,哪里要Animal表了。再者桑李,還有一些屬性在Human表里面有踱蛀,在Animal表里面沒(méi)有。這是時(shí)候就必須手動(dòng)添加一個(gè)Mapping Model文件了贵白,手動(dòng)指定哪些屬性是源實(shí)體的屬性星岗,應(yīng)該映射到目標(biāo)實(shí)體的哪個(gè)屬性上面去。這種更加精細(xì)的遷移方式戒洼,就只能用手動(dòng)添加Mapping Model來(lái)完成了俏橘,畢竟iOS系統(tǒng)不知道你的需求和想法。
三.通過(guò)代碼實(shí)現(xiàn)數(shù)據(jù)遷移
這個(gè)通過(guò)代碼進(jìn)行遷移主要是在數(shù)據(jù)遷移過(guò)程中圈浇,如果你還想做一些什么其他事情寥掐,比如說(shuō)你想清理一下垃圾數(shù)據(jù),實(shí)時(shí)展示數(shù)據(jù)遷移的進(jìn)度磷蜀,等等召耘,那就需要在這里來(lái)實(shí)現(xiàn)了。
首先褐隆,我們需要檢查一下該存儲(chǔ)區(qū)存不存在污它,再把存儲(chǔ)區(qū)里面的model metadata進(jìn)行比較,檢查一下是否兼容庶弃,如果不能兼容衫贬,那么就需要我們進(jìn)行數(shù)據(jù)遷移了。
- (BOOL)isMigrationNecessaryForStore:(NSURL*)storeUrl
{
NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
if (![[NSFileManager defaultManager] fileExistsAtPath:[self storeURL].path])
{
NSLog(@"SKIPPED MIGRATION: Source database missing.");
return NO;
}
NSError *error = nil;
NSDictionary *sourceMetadata =
[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeUrl error:&error];
NSManagedObjectModel *destinationModel = _coordinator.managedObjectModel;
if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata])
{
NSLog(@"SKIPPED MIGRATION: Source is already compatible");
return NO;
}
return YES;
}
當(dāng)上面函數(shù)返回YES歇攻,我們就需要合并了固惯,那接下來(lái)就是下面的函數(shù)了
- (BOOL)migrateStore:(NSURL*)sourceStore {
NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
BOOL success = NO;
NSError *error = nil;
// STEP 1 - 收集 Source源實(shí)體, Destination目標(biāo)實(shí)體 和 Mapping Model文件
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStore
error:&error];
NSManagedObjectModel *sourceModel =
[NSManagedObjectModel mergedModelFromBundles:nil
forStoreMetadata:sourceMetadata];
NSManagedObjectModel *destinModel = _model;
NSMappingModel *mappingModel =
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:destinModel];
// STEP 2 - 開(kāi)始執(zhí)行 migration合并, 前提是 mapping model 不是空,或者存在
if (mappingModel) {
NSError *error = nil;
NSMigrationManager *migrationManager =
[[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:destinModel];
[migrationManager addObserver:self
forKeyPath:@"migrationProgress"
options:NSKeyValueObservingOptionNew
context:NULL];
NSURL *destinStore =
[[self applicationStoresDirectory]
URLByAppendingPathComponent:@"Temp.sqlite"];
success =
[migrationManager migrateStoreFromURL:sourceStore
type:NSSQLiteStoreType options:nil
withMappingModel:mappingModel
toDestinationURL:destinStore
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:&error];
if (success)
{
// STEP 3 - 用新的migrated store替換老的store
if ([self replaceStore:sourceStore withStore:destinStore])
{
NSLog(@"SUCCESSFULLY MIGRATED %@ to the Current Model",
sourceStore.path);
[migrationManager removeObserver:self
forKeyPath:@"migrationProgress"];
}
}
else
{
NSLog(@"FAILED MIGRATION: %@",error);
}
}
else
{
NSLog(@"FAILED MIGRATION: Mapping Model is null");
}
return YES; // migration已經(jīng)完成
}
上面的函數(shù)中缴守,如果遷移進(jìn)度有變化葬毫,會(huì)通過(guò)觀察者,observeValueForKeyPath來(lái)告訴用戶(hù)進(jìn)度屡穗,這里可以監(jiān)聽(tīng)該進(jìn)度贴捡,如果沒(méi)有完成,可以來(lái)禁止用戶(hù)執(zhí)行某些操作
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"migrationProgress"]) {
dispatch_async(dispatch_get_main_queue(), ^{
float progress =
[[change objectForKey:NSKeyValueChangeNewKey] floatValue];
int percentage = progress * 100;
NSString *string =
[NSString stringWithFormat:@"Migration Progress: %i%%",
percentage];
NSLog(@"%@",string);
});
}
}
當(dāng)然村砂,這個(gè)合并數(shù)據(jù)遷移的操作肯定是用一個(gè)多線程異步的執(zhí)行烂斋,免得造成用戶(hù)界面卡頓,再加入下面的方法,我們來(lái)異步執(zhí)行
- (void)performBackgroundManagedMigrationForStore:(NSURL*)storeURL
{
NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
dispatch_async(
dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
BOOL done = [self migrateStore:storeURL];
if(done) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
_store =
[_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeURL]
options:nil
error:&error];
if (!_store) {
NSLog(@"Failed to add a migrated store. Error: %@",
error);abort();}
else {
NSLog(@"Successfully added a migrated store: %@",
_store);}
});
}
});
}
到這里源祈,數(shù)據(jù)遷移都完成了煎源,不過(guò)目前還有一個(gè)問(wèn)題就是,我們應(yīng)該何時(shí)去執(zhí)行該遷移的操作香缺,更新完畢之后手销?appDelegate一進(jìn)來(lái)?都不好图张,最好的方法還是在把當(dāng)前存儲(chǔ)區(qū)添加到coordinator之前锋拖,我們就執(zhí)行好數(shù)據(jù)遷移!
- (void)loadStore
{
NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
if (_store) {return;} // 不要再次加載了祸轮,因?yàn)橐呀?jīng)加載過(guò)了
BOOL useMigrationManager = NO;
if (useMigrationManager &&
[self isMigrationNecessaryForStore:[self storeURL]])
{
[self performBackgroundManagedMigrationForStore:[self storeURL]];
}
else
{
NSDictionary *options =
@{
NSMigratePersistentStoresAutomaticallyOption:@YES
,NSInferMappingModelAutomaticallyOption:@YES
,NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"}
};
NSError *error = nil;
_store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeURL]
options:options
error:&error];
if (!_store)
{
NSLog(@"Failed to add store. Error: %@", error);abort();
}
else
{
NSLog(@"Successfully added store: %@", _store);
}
}
}
這樣就完成了數(shù)據(jù)遷移了兽埃,并且還能顯示出遷移進(jìn)度,在遷移中還可以自定義一些操作适袜,比如說(shuō)清理垃圾數(shù)據(jù)柄错,刪除一些不用的表,等等苦酱。
結(jié)束
好了售貌,到此,Core Data數(shù)據(jù)遷移的幾種方式我就和大家分享完了疫萤,如果文中有不對(duì)的地方颂跨,歡迎大家提出來(lái),我們一起交流進(jìn)步扯饶!