鍍金池/ 教程/ iOS/ 自定義 Core Data 遷移
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

自定義 Core Data 遷移

自定義 Core Data 遷移似乎是一個(gè)不太起眼的話題。蘋果在這方面只提供了很少的文檔,若是初次涉足此方面內(nèi)容,很可能會(huì)變成一個(gè)可怕的經(jīng)歷。鑒于客戶端程序的性質(zhì),你無法測(cè)試你的用戶所生成的數(shù)據(jù)集的所有可能排列。此外,解決遷移過程中出現(xiàn)的問題會(huì)很困難,而因?yàn)闃O有可能你的代碼依賴于最新的數(shù)據(jù)模型,所以回退并不是一個(gè)可選的處理辦法。

在本文中,我們將走一遍搭建自定義 Core Data 遷移的過程,并著重于數(shù)據(jù)模型的重構(gòu)。我們將探討從舊模型中提取數(shù)據(jù)并使用這些數(shù)據(jù)來填充具有新的實(shí)體和關(guān)系的目標(biāo)模型。此外,會(huì)有一個(gè)包含單元測(cè)試的示例項(xiàng)目用于演示兩個(gè)自定義遷移。

需要注意的是,如果對(duì)數(shù)據(jù)模型的修改只有增加一個(gè)實(shí)體或可選屬性,輕量級(jí)的遷移是一個(gè)很好的選擇。它們非常易于設(shè)置,所以本文只會(huì)稍稍提及它們。若想知道輕量級(jí)遷移的應(yīng)用場(chǎng)合,請(qǐng)查看官方文檔

這就是說,如果你需要快速地在你的數(shù)據(jù)模型上進(jìn)行相對(duì)復(fù)雜的改變,那么自定義遷移就是為你準(zhǔn)備的。

映射模型 (Mapping Models)

當(dāng)你要升級(jí)你的數(shù)據(jù)模型到新版,你將先選擇一個(gè)基準(zhǔn)模型。對(duì)于輕量級(jí)遷移,持久化存儲(chǔ)會(huì)為你自動(dòng)推斷一個(gè)映射模型。然而,如果你對(duì)新模型所做的修改并不被輕量級(jí)遷移所支持,那么你就需要?jiǎng)?chuàng)建一個(gè)映射模型。一個(gè)映射模型需要一個(gè)源數(shù)據(jù)模型和一個(gè)目標(biāo)數(shù)據(jù)模型。 NSMigrationManager 能夠推斷這兩個(gè)模型間的映射模型。這使得它很誘人,可用來一路創(chuàng)建每一個(gè)以前的模型到最新模型之間的映射模型,但這很快就會(huì)變成一團(tuán)亂麻。對(duì)于每一個(gè)新版模型,你需要?jiǎng)?chuàng)建的映射模型的量將線性增長。這可能看起來不是個(gè)大問題,但隨之而來的是測(cè)試這些映射模型的復(fù)雜度大大提高了。

想像一下你剛剛部署一個(gè)包含版本 3 的數(shù)據(jù)模型的更新。你的某個(gè)用戶已經(jīng)有一段時(shí)間沒有更新你的應(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ò)展性很差,那就來試試漸進(jìn)式遷移吧。

漸進(jìn)式遷移 (Progressive Migrations)

與其為每個(gè)之前的數(shù)據(jù)模型到最新的模型間都建立映射模型,還不如在每兩個(gè)連續(xù)的數(shù)據(jù)模型之間創(chuàng)建映射模型。以前面的例子來說,版本 1 和版本 2 之間需要一個(gè)映射模型,版本 2 和版本 3 之間需要一個(gè)映射模型。這樣就可以從版本 1 遷移到版本 2 再遷移到版本 3。顯然,使用這種遷移的方式時(shí),若用戶在較老的版本上遷移過程就會(huì)比較慢,但它能節(jié)省開發(fā)時(shí)間并保證健壯性,因?yàn)槟阒恍枰_保從之前一個(gè)模型到新模型的遷移工作正常即可,而更前面的映射模型都已經(jīng)經(jīng)過了測(cè)試。

總的想法就是手動(dòng)找出當(dāng)前版本 v 和版本 v+1 之間的映射模型,在這兩者間遷移,接著繼續(xù)遞歸,直到持久化存儲(chǔ)與當(dāng)前的數(shù)據(jù)模型兼容。

這一過程看起來像下面這樣(完整版可以在示例項(xiàng)目里找到):

- (BOOL)progressivelyMigrateURL:(NSURL *)sourceStoreURL
                         ofType:(NSString *)type
                        toModel:(NSManagedObjectModel *)finalModel
                          error:(NSError **)error
{
    NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type
                                                                                              URL:sourceStoreURL
                                                                                            error:error];
    if (!sourceMetadata) {
        return NO;
    }
    if ([finalModel isConfiguration:nil
        compatibleWithStoreMetadata:sourceMetadata]) {
        if (NULL != error) {
            *error = nil;
        }
        return YES;
    }
    NSManagedObjectModel *sourceModel = [self sourceModelForSourceMetadata:sourceMetadata];
    NSManagedObjectModel *destinationModel = nil;
    NSMappingModel *mappingModel = nil;
    NSString *modelName = nil;
    if (![self getDestinationModel:&destinationModel
                      mappingModel:&mappingModel
                         modelName:&modelName
                    forSourceModel:sourceModel
                             error:error]) {
        return NO;
    }
    // 我們現(xiàn)在有了一個(gè)映射模型,開始遷移
    NSURL *destinationStoreURL = [self destinationStoreURLWithSourceStoreURL:sourceStoreURL
                                                                   modelName:modelName];
    NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel
                                                                 destinationModel:destinationModel];
    if (![manager migrateStoreFromURL:sourceStoreURL
                                 type:type
                              options:nil
                     withMappingModel:mappingModel
                     toDestinationURL:destinationStoreURL
                      destinationType:type
                   destinationOptions:nil
                                error:error]) {
        return NO;
    }
    // 現(xiàn)在遷移成功了,把文件備份一下以防不測(cè)
    if (![self backupSourceStoreAtURL:sourceStoreURL
          movingDestinationStoreAtURL:destinationStoreURL
                                error:error]) {
        return NO;
    }
    // 現(xiàn)在數(shù)據(jù)模型可能還不是“最新”版,所以接著遞歸
    return [self progressivelyMigrateURL:sourceStoreURL
                                  ofType:type
                                 toModel:finalModel
                                   error:error];
}

這段代碼主要來源于 Marcus Zarra,他寫了一本很棒的關(guān)于 Core Data 的書,查看這里。

自 iOS 7 和 OS Mavericks以來,Apple 將 SQLite 的日志模式改寫為預(yù)寫式日志 (Write-Ahead Logging), 這意味著數(shù)據(jù)庫事務(wù)都被依附到一個(gè) -wal 文件中。這有可能導(dǎo)致數(shù)據(jù)丟失和異常。為了數(shù)據(jù)的安全,我們會(huì)將日志模式改寫為回溯模式。而如果我們想要遷移數(shù)據(jù)(或者為了以后備份),我們可以將一個(gè)字典傳遞給 -addPersistentStoreWithType:configuration:URL:options:error: 來完成改寫。

@{ NSSQLitePragmasOption: @{ @"journal_mode": @"DELETE” } }

NSPersistentStoreCoordinator 相關(guān)的代碼可以在這里找到。

遷移策略

NSEntityMigrationPolicy 是自定義遷移過程的核心。 蘋果的文檔中有這么一句話:

NSEntityMigrationPolicy 的實(shí)例為一個(gè)實(shí)體映射自定義的遷移策略。

簡單的說,這個(gè)類讓我們不僅僅能修改實(shí)體的屬性和關(guān)系,而且還能任意添加一些自定義的操作來完成每個(gè)實(shí)體的遷移。

遷移示例

假設(shè)我們有一個(gè)帶有簡單的數(shù)據(jù)模型的書籍應(yīng)用。這個(gè)模型有兩個(gè)實(shí)體: UserBookBook 實(shí)體有一個(gè)屬性叫做 authorName。我們想改善這個(gè)模型,添加一個(gè)新的實(shí)體: Author。同時(shí)我們想為 BookAuthor 建立一個(gè)多對(duì)多的關(guān)系,因?yàn)橐槐緯捎卸鄠€(gè)作者,而一個(gè)作者也可寫多本書籍。我們將從 Book 對(duì)象里取出 authorName 用于填充一個(gè)新的實(shí)體并建立關(guān)系。

一開始我們要做的是基于第一個(gè)數(shù)據(jù)模型增加一個(gè)新版模型。在這個(gè)例子里,我們添加了一個(gè) Author 實(shí)體,它與 Book 還有多對(duì)多的關(guān)系。

http://wiki.jikexueyuan.com/project/objc/images/4-3.png" alt="" />

現(xiàn)在數(shù)據(jù)模型已經(jīng)是我們所需要的,但我們還需要遷移所有已存在的數(shù)據(jù),這就該 NSEntityMigrationPolicy 出場(chǎng)了。我們創(chuàng)建 NSEntityMigrationPolicy 的一個(gè)子類---- MHWBookToBookPolicy 。在映射模型里,我們選擇 Book 實(shí)體并設(shè)置它作為公共部分(Utilities section)中的自定義策略。

http://wiki.jikexueyuan.com/project/objc/images/4-4.png" alt="" />

同時(shí)我們使用 user info 字典來設(shè)置一個(gè) modelVersion ,它將在未來的遷移中派上用場(chǎng)。

MHWBookToBookPolicy 中,我們將重載 -createDestinationInstancesForSourceInstance:entityMapping:manager:error: 方法,它允許我們自定義如何遷移每個(gè) Book 實(shí)例。如果 modelVersion 的值不是 2,我們將調(diào)用父類的實(shí)現(xiàn),否則我們就要做自定義遷移。我們插入基于映射的目標(biāo)實(shí)體的新 NSManagedObject 對(duì)象到目標(biāo)上下文。然后我們遍歷目標(biāo)實(shí)例的屬性鍵值并與來自源實(shí)例的值一起填充它們。這將保證我們保留現(xiàn)存數(shù)據(jù)并避免設(shè)置任何我們已經(jīng)在目標(biāo)實(shí)例中移除的值。

NSNumber *modelVersion = [mapping.userInfo valueForKey:@"modelVersion"];
if (modelVersion.integerValue == 2) {
    NSMutableArray *sourceKeys = [sourceInstance.entity.attributesByName.allKeys mutableCopy];
    NSDictionary *sourceValues = [sourceInstance dictionaryWithValuesForKeys:sourceKeys];
    NSManagedObject *destinationInstance = [NSEntityDescription insertNewObjectForEntityForName:mapping.destinationEntityName
                                                                         inManagedObjectContext:manager.destinationContext];
    NSArray *destinationKeys = destinationInstance.entity.attributesByName.allKeys;
    for (NSString *key in destinationKeys) {
        id value = [sourceValues valueForKey:key];
        // 避免value為空
        if (value && ![value isEqual:[NSNull null]]) {
            [destinationInstance setValue:value forKey:key];
        }
    }
}

然后我們將基于源實(shí)例的值創(chuàng)建一個(gè) Author 實(shí)體。但若多本書有同一個(gè)作者會(huì)發(fā)生什么呢?我們將使用 NSMigrationManager 的一個(gè) category 方法來創(chuàng)建一個(gè)查找字典,確保對(duì)于同一個(gè)名字的作者,我們只會(huì)創(chuàng)建一個(gè) Author。

NSMutableDictionary *authorLookup = [manager lookupWithKey:@"authors"];
// 檢查該作者是否已經(jīng)被創(chuàng)建了
NSString *authorName = [sourceInstance valueForKey:@"author"];
NSManagedObject *author = [authorLookup valueForKey:authorName];
if (!author) {
    // 創(chuàng)建作者
    // ...

    // 更新避免重復(fù)
    [authorLookup setValue:author forKey:authorName];
}
[destinationInstance performSelector:@selector(addAuthorsObject:) withObject:author];

最后,我們需要告訴遷移管理器在源存儲(chǔ)與目的存儲(chǔ)之間關(guān)聯(lián)數(shù)據(jù):

[manager associateSourceInstance:sourceInstance
         withDestinationInstance:destinationInstance
                forEntityMapping:mapping];
return YES;

NSMigrationManager 的 category 方法:

@implementation NSMigrationManager (Lookup)

- (NSMutableDictionary *)lookupWithKey:(NSString *)lookupKey
{
    NSMutableDictionary *userInfo = (NSMutableDictionary *)self.userInfo;
    // 這里檢查一下是否已經(jīng)建立了 userInfo 的字典
    if (!userInfo) {
        userInfo = [@{} mutableCopy];
        self.userInfo = userInfo;
    }
    NSMutableDictionary *lookup = [userInfo valueForKey:lookupKey];
    if (!lookup) {
        lookup = [@{} mutableCopy];
        [userInfo setValue:lookup forKey:lookupKey];
    }
    return lookup;
}

@end

一個(gè)更復(fù)雜的遷移

過了一會(huì),我們又想把 fileURL 這個(gè)屬性從 Book 實(shí)體里提出來,放入一個(gè)叫做 File 的新實(shí)體里。同時(shí)我們還想修改實(shí)體之間的關(guān)系,以便 User 可與 File 有一對(duì)多的關(guān)系,而反過來 FileBook 有多對(duì)一的關(guān)系。

http://wiki.jikexueyuan.com/project/objc/images/4-5.png" alt="" />

在之前的遷移中,我們只遷移了一個(gè)實(shí)體。而現(xiàn)在當(dāng)我們添加了 File 后,事情變得有些復(fù)雜了。我們不能簡單地在遷移一個(gè) Book 時(shí)插入一個(gè) File 實(shí)體并設(shè)置它與 User 的對(duì)應(yīng)關(guān)系,因?yàn)榇藭r(shí) User 實(shí)體還沒有被遷移,之間的關(guān)系也無從談起。我們必須考慮遷移的執(zhí)行順序。在映射模型中,是可以改變實(shí)體映射的順序的。具體到這里的例子,我們想將 UserToUser 映射放在 BookToBook 映射之上。這保證了 User 實(shí)體會(huì)比 Book 實(shí)體更早遷移。

http://wiki.jikexueyuan.com/project/objc/images/4-6.png" alt="" />

添加一個(gè) File 實(shí)體的途徑和創(chuàng)建 Author 的過程相似。我們?cè)?MHWBookToBookPolicy 中遷移 Book 實(shí)體時(shí)創(chuàng)建 File 對(duì)象。我們會(huì)查看源實(shí)例的 User 實(shí)體,為每個(gè) User 實(shí)體創(chuàng)建一個(gè)新的 File 對(duì)象,并建立對(duì)應(yīng)關(guān)系:

NSArray *users = [sourceInstance valueForKey:@"users"];
for (NSManagedObject *user in users) {

    NSManagedObject *file = [NSEntityDescription insertNewObjectForEntityForName:@"File"
                                                          inManagedObjectContext:manager.destinationContext];
    [file setValue:[sourceInstance valueForKey:@"fileURL"] forKey:@"fileURL"];
    [file setValue:destinationInstance forKey:@"book"];

    NSInteger userId = [[user valueForKey:@"userId"] integerValue];
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"User"];
    request.predicate = [NSPredicate predicateWithFormat:@"userId = %d", userId];
    NSManagedObject *user = [[manager.destinationContext executeFetchRequest:request error:nil] lastObject];
    [file setValue:user forKey:@"user"];
}

大數(shù)據(jù)集

如果你的存儲(chǔ)包含了大量數(shù)據(jù),以至到達(dá)一個(gè)臨界點(diǎn),遷移就會(huì)消耗過多內(nèi)存,Core Data 提供了一個(gè)以數(shù)據(jù)塊(chunks)的方式遷移的辦法。蘋果的文檔有簡要地提到這件事。解決辦法是使用多映射模型分開你的遷移并為每個(gè)映射模型遷移一次。這要求你有一個(gè)對(duì)象圖(object graph),在其中,遷移可被分為兩個(gè)或多個(gè)部分。為了支持這一點(diǎn)而需要添加的代碼其實(shí)很少。

首先,我們更新遷移方法以支持使用多個(gè)映射模型來遷移。已知映射模型的順序很重要,我們將通過代理方法請(qǐng)求它們:

NSArray *mappingModels = @[mappingModel]; // 我們之前建立的那個(gè)模型
if ([self.delegate respondsToSelector:@selector(migrationManager:mappingModelsForSourceModel:)]) {
    NSArray *explicitMappingModels = [self.delegate migrationManager:self
                                         mappingModelsForSourceModel:sourceModel];
    if (0 < explicitMappingModels.count) {
        mappingModels = explicitMappingModels;
    }
}
for (NSMappingModel *mappingModel in mappingModels) {
    didMigrate = [manager migrateStoreFromURL:sourceStoreURL
                                         type:type
                                      options:nil
                             withMappingModel:mappingModel
                             toDestinationURL:destinationStoreURL
                              destinationType:type
                           destinationOptions:nil
                                        error:error];
}

現(xiàn)在,我們?nèi)绾沃獣阅囊粋€(gè)映射模型被用于這個(gè)特定的源模型呢?此處的 API 可能顯得有些笨拙,但以下的解決方法確實(shí)完成了工作。在代理方法中,我們找出源模型的名字并返回相關(guān)的映射模型:

- (NSArray *)migrationManager:(MHWMigrationManager *)migrationManager 
  mappingModelsForSourceModel:(NSManagedObjectModel *)sourceModel
{
    NSMutableArray *mappingModels = [@[] mutableCopy];
    NSString *modelName = [sourceModel mhw_modelName];
    if ([modelName isEqual:@"Model2"]) {
        // 把該映射模型加入數(shù)組
    }
    return mappingModels;
}

我們將為 NSManagedObjectModel 添加一個(gè) category,以幫助我們找出它的文件名: We’ll add a category on NSManagedObjectModel that helps us figure out its filename:

- (NSString *)mhw_modelName
{
    NSString *modelName = nil;
    NSArray *modelPaths = // get paths to all the mom files in the bundle
    for (NSString *modelPath in modelPaths) {
        NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
        NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
        if ([model isEqual:self]) {
            modelName = modelURL.lastPathComponent.stringByDeletingPathExtension;
            break;
        }
    }
    return modelName;
}

由于 User 在前面的例子(沒有源關(guān)系映射)中被從對(duì)象圖中隔離,因此遷移 User 的過程將省事很多。我們將從第一個(gè)映射模型中移除 UserToUser 映射,然后創(chuàng)建一個(gè)僅有 UserToUser 的映射。不要忘記在映射模型列表中返回新的 User 映射模型,因?yàn)槲覀冋谄渌成渲性O(shè)置新關(guān)系

單元測(cè)試

為此應(yīng)用建立單元測(cè)試異常簡單:

  1. 將相關(guān)數(shù)據(jù)填入舊存儲(chǔ)*。
  2. 將產(chǎn)生的持久性存儲(chǔ)文件復(fù)制到你的測(cè)試目標(biāo)。
  3. 編寫測(cè)試斷言符合最新的數(shù)據(jù)模型。
  4. 運(yùn)行測(cè)試,遷移數(shù)據(jù)到新的數(shù)據(jù)模型。

*這很容易完成,只需在模擬器里運(yùn)行一下你應(yīng)用最新的版本(production version)即可

步驟 1 和 2 很簡單。步驟 3 留給讀者作為練習(xí),然后我會(huì)引導(dǎo)你通過第 4 步。

當(dāng)持久化存儲(chǔ)文件被添加到單元測(cè)試目標(biāo)上時(shí),我們需要告知遷移管理器把那個(gè)存儲(chǔ)遷移至我們的目標(biāo)存儲(chǔ)。在示例項(xiàng)目中所示如下:

- (void)setUpCoreDataStackMigratingFromStoreWithName:(NSString *)name
{
    NSURL *storeURL = [self temporaryRandomURL];
    [self copyStoreWithName:name toURL:storeURL];

    NSURL *momURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
    self.managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];

    NSString *storeType = NSSQLiteStoreType;

    MHWMigrationManager *migrationManager = [MHWMigrationManager new];
    [migrationManager progressivelyMigrateURL:storeURL
                                       ofType:storeType
                                      toModel:self.managedObjectModel
                                        error:nil];

    self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
    [self.persistentStoreCoordinator addPersistentStoreWithType:storeType
                                                  configuration:nil
                                                            URL:storeURL
                                                        options:nil
                                                          error:nil];

    self.managedObjectContext = [[NSManagedObjectContext alloc] init];
    self.managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
}

- (NSURL *)temporaryRandomURL
{
    NSString *uniqueName = [NSProcessInfo processInfo].globallyUniqueString;
    return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingString:uniqueName]];
}

- (void)copyStoreWithName:(NSString *)name toURL:(NSURL *)url
{
    // 每次創(chuàng)建一個(gè)唯一的url以保證測(cè)試正常運(yùn)行
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    NSFileManager *fileManager = [NSFileManager new];
    NSString *path = [bundle pathForResource:[name stringByDeletingPathExtension] ofType:name.pathExtension];
    [fileManager copyItemAtPath:path
                         toPath:url.path error:nil];
}

把下面的代碼放到一個(gè)父類,以便于在測(cè)試的類中復(fù)用:

- (void)setUp
{
    [super setUp];
    [self setUpCoreDataStackMigratingFromStoreWithName:@"Model1.sqlite"];
}

結(jié)論

輕量級(jí)遷移是直接在 SQLite 內(nèi)部發(fā)生。這相對(duì)于自定義遷移來說非??焖偾矣行?。自定義遷移要把源對(duì)象讀入到內(nèi)存中,然后拷貝值到目標(biāo)對(duì)象,重新建立關(guān)系,最后插入到新的存儲(chǔ)中。這樣做不僅很慢,而且當(dāng)遷移大數(shù)據(jù)集時(shí),由于內(nèi)存大小的限制,它還會(huì)引起系統(tǒng)強(qiáng)制回收內(nèi)存問題。

添加數(shù)據(jù)前盡量考慮完全

在處理任何數(shù)據(jù)持久性問題時(shí)最重要的事情之一就是仔細(xì)思考你的模型。我們希望模型是可持續(xù)發(fā)展的。在最開始創(chuàng)建模型的時(shí)候盡量考慮完全。添加空屬性或者空實(shí)體也比以后進(jìn)行遷移時(shí)候創(chuàng)建好的多,因?yàn)檫w移很容易出現(xiàn)錯(cuò)誤,而未使用的數(shù)據(jù)就不會(huì)了。

調(diào)試遷移

測(cè)試遷移時(shí)一個(gè)有用的啟動(dòng)參數(shù)是 -com.apple.CoreData.MigrationDebug。設(shè)置為 1 時(shí),你會(huì)在控制臺(tái)收到關(guān)于遷移數(shù)據(jù)時(shí)特殊情況的信息。如果你熟悉 SQL 但不了解 Core Data,設(shè)置 -com.apple.CoreData.SQLDebug 為 1 可在控制臺(tái)看到實(shí)際操作的 SQL 語句。

上一篇:同步數(shù)據(jù)下一篇:動(dòng)畫