認(rèn)識CoreData - 高級用法

該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請注明:劉小壯

配圖

在之前的文章中姨俩,已經(jīng)講了很多關(guān)于CoreData使用相關(guān)的知識點(diǎn)。這篇文章中主要講兩個(gè)方面朗儒,NSFetchedResultsController和版本遷移查坪。

文章題目中雖然有“高級”兩個(gè)字,其實(shí)講的東西并不高級歧蒋,只是因?yàn)樯弦黄恼轮袞|西太多了土砂,把兩個(gè)較復(fù)雜的知識點(diǎn)挪到這篇文章中。??

文章中如有疏漏或錯(cuò)誤谜洽,還請各位及時(shí)提出萝映,謝謝!??


NSFetchedResultsController

在開發(fā)過程中會經(jīng)常用到UITableView這樣的視圖類阐虚,這些視圖類需要自己管理其數(shù)據(jù)源序臂,包括網(wǎng)絡(luò)獲取、本地存儲都需要寫代碼進(jìn)行管理实束。

而在CoreData中提供了NSFetchedResultsController類(fetched results controller奥秆,也叫FRC)逊彭,FRC可以管理UITableViewUICollectionView的數(shù)據(jù)源吭练。這個(gè)數(shù)據(jù)源主要指本地持久化的數(shù)據(jù)诫龙,也可以用這個(gè)數(shù)據(jù)源配合著網(wǎng)絡(luò)請求數(shù)據(jù)一起使用,主要看業(yè)務(wù)需求了鲫咽。

本篇文章會使用UITableView作為視圖類签赃,配合NSFetchedResultsController進(jìn)行后面的演示,UICollectionView配合NSFetchedResultsController的使用也是類似分尸,這里就不都講了锦聊。

簡單介紹

就像上面說到的,NSFetchedResultsController就像是上面兩種視圖的數(shù)據(jù)管理者一樣箩绍。FRC可以監(jiān)聽一個(gè)MOC的改變孔庭,如果MOC執(zhí)行了托管對象的增刪改操作,就會對本地持久化數(shù)據(jù)發(fā)生改變材蛛,FRC就會回調(diào)對應(yīng)的代理方法圆到,回調(diào)方法的參數(shù)會包括執(zhí)行操作的類型、操作的值卑吭、indexPath等參數(shù)芽淡。

實(shí)際使用時(shí),通過FRC“綁定”一個(gè)MOC豆赏,將UITableView嵌入在FRC的執(zhí)行流程中挣菲。在任何地方對這個(gè)“綁定”MOC存儲區(qū)做修改,都會觸發(fā)FRC的回調(diào)方法掷邦,在FRC的回調(diào)方法中嵌入UITableView代碼并做對應(yīng)修改即可白胀。

由此可以看出FRC最大優(yōu)勢就是,始終和本地持久化的數(shù)據(jù)保持統(tǒng)一抚岗。只要本地持久化的數(shù)據(jù)發(fā)生改變或杠,就會觸發(fā)FRC的回調(diào)方法,從而在回調(diào)方法中更新上層數(shù)據(jù)源和UI宣蔚。這種方式講的簡單一點(diǎn)廷痘,就可以叫做數(shù)據(jù)帶動(dòng)UI

FRC

但是需要注意一點(diǎn)件已,在FRC的初始化中傳入了一個(gè)MOC參數(shù)笋额,FRC只能監(jiān)測傳入的MOC發(fā)生的改變。假設(shè)其他MOC對同一個(gè)存儲區(qū)發(fā)生了改變篷扩,FRC則不能監(jiān)測到這個(gè)變化兄猩,不會做出任何反應(yīng)。

所以使用FRC時(shí),需要注意FRC只能對一個(gè)MOC的變化做出反應(yīng)枢冤,所以在CoreData持久化層設(shè)計(jì)時(shí)鸠姨,盡量一個(gè)存儲區(qū)只對應(yīng)一個(gè)MOC,或設(shè)置一個(gè)負(fù)責(zé)UIMOC淹真,這在后面多線程部分會詳細(xì)講解讶迁。

修改模型文件結(jié)構(gòu)

在寫代碼之前,先對之前的模型文件結(jié)構(gòu)做一些修改核蘸。

Employee結(jié)構(gòu)

FRC的時(shí)候巍糯,只需要用到Employee這一張表,其他表和設(shè)置直接忽略客扎。需要在Employee原有字段的基礎(chǔ)上祟峦,增加一個(gè)String類型的sectionName字段,這個(gè)字段就是用來存儲section title的徙鱼,在下面的文章中將會詳細(xì)講到宅楞。

初始化FRC

下面例子是比較常用的FRC初始化方式,初始化時(shí)指定的MOC袱吆,還用之前講過的MOC初始化代碼厌衙,UITableView初始化代碼這里也省略了,主要突出FRC的初始化绞绒。

// 創(chuàng)建請求對象婶希,并指明操作Employee表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 設(shè)置排序規(guī)則,指明根據(jù)height字段升序排序
NSSortDescriptor *heightSort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
request.sortDescriptors = @[heightSort];

// 創(chuàng)建NSFetchedResultsController控制器實(shí)例处铛,并綁定MOC
NSError *error = nil;
fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request 
                                                              managedObjectContext:context 
                                                                sectionNameKeyPath:@"sectionName" 
                                                                         cacheName:nil];
// 設(shè)置代理饲趋,并遵守協(xié)議
fetchedResultController.delegate = self;
// 執(zhí)行獲取請求拐揭,執(zhí)行后FRC會從持久化存儲區(qū)加載數(shù)據(jù)撤蟆,其他地方可以通過FRC獲取數(shù)據(jù)
[fetchedResultController performFetch:&error];

// 錯(cuò)誤處理
if (error) {
    NSLog(@"NSFetchedResultsController init error : %@", error);
}

// 刷新UI
[tableView reloadData];

在上面初始化FRC時(shí),傳入的sectionNameKeyPath:參數(shù)堂污,是指明當(dāng)前托管對象的哪個(gè)屬性當(dāng)做sectiontitle家肯,在本文中就是Employee表的sectionName字段為sectiontitle。從NSFetchedResultsSectionInfo協(xié)議的indexTitle屬性獲取這個(gè)值盟猖。

sectionNameKeyPath:設(shè)置屬性名后讨衣,就以這個(gè)屬性名作為分組title,相同的title會被分到一個(gè)section中式镐。

初始化FRC時(shí)參數(shù)managedObjectContext:傳入了一個(gè)MOC參數(shù)反镇,FRC只能監(jiān)測這個(gè)傳入的MOC發(fā)生的本地持久化改變。就像上面介紹時(shí)說的娘汞,其他MOC對同一個(gè)持久化存儲區(qū)發(fā)生的改變歹茶,FRC則不能監(jiān)測到這個(gè)變化。

再往后面看到cacheName:參數(shù),這個(gè)參數(shù)我設(shè)置的是nil惊豺。參數(shù)的作用是開啟FRC的緩存燎孟,對獲取的數(shù)據(jù)進(jìn)行緩存并指定一個(gè)名字∈粒可以通過調(diào)用deleteCacheWithName:方法手動(dòng)刪除緩存揩页。

但是這個(gè)緩存并沒有必要,緩存是根據(jù)NSFetchRequest對象來匹配的烹俗,如果當(dāng)前獲取的數(shù)據(jù)和之前緩存的相匹配則直接拿來用爆侣,但是在獲取數(shù)據(jù)時(shí)每次獲取的數(shù)據(jù)都可能不同,緩存不能被命中則很難派上用場衷蜓,而且緩存還占用著內(nèi)存資源累提。

FRC初始化完成后,調(diào)用performFetch:方法來同步獲取持久化存儲區(qū)數(shù)據(jù)磁浇,調(diào)用此方法后FRC保存數(shù)據(jù)的屬性才會有值斋陪。獲取到數(shù)據(jù)后,調(diào)用tableViewreloadData方法置吓,會回調(diào)tableView的代理方法无虚,可以在tableView的代理方法中獲取到FRC的數(shù)據(jù)。調(diào)用performFetch:方法第一次獲取到數(shù)據(jù)并不會回調(diào)FRC代理方法衍锚。

代理方法

FRC中包含UITableView執(zhí)行過程中需要的相關(guān)數(shù)據(jù)友题,可以通過FRCsections屬性,獲取一個(gè)遵守<NSFetchedResultsSectionInfo>協(xié)議的對象數(shù)組戴质,數(shù)組中的對象就代表一個(gè)section度宦。

在這個(gè)協(xié)議中有如下定義,可以看出這些屬性和UITableView的執(zhí)行流程是緊密相關(guān)的告匠。

@protocol NSFetchedResultsSectionInfo

/* Name of the section */
@property (nonatomic, readonly) NSString *name;

/* Title of the section (used when displaying the index) */
@property (nullable, nonatomic, readonly) NSString *indexTitle;

/* Number of objects in section */
@property (nonatomic, readonly) NSUInteger numberOfObjects;

/* Returns the array of objects in the section. */
@property (nullable, nonatomic, readonly) NSArray *objects;

@end // NSFetchedResultsSectionInfo

在使用過程中應(yīng)該將FRCUITableView相互嵌套戈抄,在FRC的回調(diào)方法中嵌套UITableView的視圖改變邏輯,在UITableView的回調(diào)中嵌套數(shù)據(jù)更新的邏輯后专。這樣可以始終保證數(shù)據(jù)和UI的同步划鸽,在下面的示例代碼中將會演示FRCUITableView的相互嵌套。

Table View Delegate
// 通過FRC的sections數(shù)組屬性戚哎,獲取所有section的count值
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return fetchedResultController.sections.count;
}

// 通過當(dāng)前section的下標(biāo)從sections數(shù)組中取出對應(yīng)的section對象裸诽,并從section對象中獲取所有對象count
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return fetchedResultController.sections[section].numberOfObjects;
}

// FRC根據(jù)indexPath獲取托管對象,并給cell賦值
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Employee *emp = [fetchedResultController objectAtIndexPath:indexPath];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifier" forIndexPath:indexPath];
    cell.textLabel.text = emp.name;
    return cell;
}

// 創(chuàng)建FRC對象時(shí)型凳,通過sectionNameKeyPath:傳遞進(jìn)去的section title的屬性名丈冬,在這里獲取對應(yīng)的屬性值
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return fetchedResultController.sections[section].indexTitle;
}

// 是否可以編輯
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

// 這里是簡單模擬UI刪除cell后,本地持久化區(qū)數(shù)據(jù)和UI同步的操作甘畅。在調(diào)用下面MOC保存上下文方法后埂蕊,F(xiàn)RC會回調(diào)代理方法并更新UI
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
      // 刪除托管對象
        Employee *emp = [fetchedResultController objectAtIndexPath:indexPath];
      [context deleteObject:emp];
      // 保存上下文環(huán)境实夹,并做錯(cuò)誤處理
      NSError *error = nil;
      if (![context save:&error]) {
          NSLog(@"tableView delete cell error : %@", error);
      }
 }
}

上面是UITableView的代理方法绳泉,代理方法中嵌套了FRC的數(shù)據(jù)獲取代碼供璧,這樣在刷新視圖時(shí)就可以保證使用最新的數(shù)據(jù)。并且在代碼中簡單實(shí)現(xiàn)了刪除cell后拷姿,通過MOC調(diào)用刪除操作匀们,使本地持久化數(shù)據(jù)和UI保持一致缴淋。

就像上面cellForRowAtIndexPath:方法中使用的一樣,FRC提供了兩個(gè)方法輕松轉(zhuǎn)換indexPathNSManagedObject的對象泄朴,在實(shí)際開發(fā)中這兩個(gè)方法非常實(shí)用重抖,這也是FRCUITableViewUICollectionView深度融合的表現(xiàn)祖灰。

- (id)objectAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)indexPathForObject:(id)object;
Fetched Results Controller Delegate
// Cell數(shù)據(jù)源發(fā)生改變會回調(diào)此方法钟沛,例如添加新的托管對象等
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath {

    switch (type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeUpdate: {
            UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
            Employee *emp = [fetchedResultController objectAtIndexPath:indexPath];
            cell.textLabel.text = emp.name;
        }
            break;
    }
}

// Section數(shù)據(jù)源發(fā)生改變回調(diào)此方法,例如修改section title等局扶。
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    switch (type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        default:
            break;
    }
}

// 本地?cái)?shù)據(jù)源發(fā)生改變恨统,將要開始回調(diào)FRC代理方法。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [tableView beginUpdates];
}

// 本地?cái)?shù)據(jù)源發(fā)生改變三妈,F(xiàn)RC代理方法回調(diào)完成畜埋。
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [tableView endUpdates];
}

// 返回section的title,可以在這里對title做進(jìn)一步處理畴蒲。這里修改title后悠鞍,對應(yīng)section的indexTitle屬性會被更新。
- (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName {
    return [NSString stringWithFormat:@"sectionName %@", sectionName];
}

上面就是當(dāng)本地持久化數(shù)據(jù)發(fā)生改變后模燥,被回調(diào)的FRC代理方法的實(shí)現(xiàn)咖祭,可以在對應(yīng)的實(shí)現(xiàn)中完成自己的代碼邏輯。

在上面的章節(jié)中講到刪除cell后蔫骂,本地持久化數(shù)據(jù)同步的問題么翰。在刪除cell后在tableView代理方法的回調(diào)中,調(diào)用了MOC的刪除方法纠吴,使本地持久化存儲和UI保持同步硬鞍,并回調(diào)到下面的FRC代理方法中慧瘤,在代理方法中對UI做刪除操作戴已,這樣一套由UI的改變引發(fā)的刪除流程就完成了。

目前為止已經(jīng)實(shí)現(xiàn)了數(shù)據(jù)和UI雙向同步锅减,即UI發(fā)生改變后本地存儲發(fā)生改變糖儡,本地存儲發(fā)生改變后UI也隨之改變≌唬可以通過下面添加數(shù)據(jù)的代碼來測試一下握联,NSFetchedResultsController就講到這里了桦沉。

- (void)addMoreData {
    Employee *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
    employee.name = [NSString stringWithFormat:@"lxz 15"];
    employee.height = @(15);
    employee.brithday = [NSDate date];
    employee.sectionName = [NSString stringWithFormat:@"3"];

    NSError *error = nil;
    if (![context save:&error]) {
        NSLog(@"MOC save error : %@", error);
    }
}   

版本遷移

CoreData版本遷移的方式有很多,一般都是先在Xcode中金闽,原有模型文件的基礎(chǔ)上纯露,創(chuàng)建一個(gè)新版本的模型文件,然后在此基礎(chǔ)上做不同方式的版本遷移代芜。

本章節(jié)將會講三種不同的版本遷移方案埠褪,但都不會講太深,都是從使用的角度講起挤庇,可以滿足大多數(shù)版本遷移的需求钞速。

為什么要版本遷移?

在已經(jīng)運(yùn)行程序并通過模型文件生成數(shù)據(jù)庫后嫡秕,再對模型文件進(jìn)行的修改渴语,如果只是修改已有實(shí)體屬性的默認(rèn)值、最大最小值昆咽、Fetch Request等屬性自身包含的參數(shù)時(shí)驾凶,并不會發(fā)生錯(cuò)誤。如果修改模型文件的結(jié)構(gòu)掷酗,或修改屬性名狭郑、實(shí)體名等,造成模型文件的結(jié)構(gòu)發(fā)生改變汇在,這樣再次運(yùn)行程序就會導(dǎo)致崩潰翰萨。

在開發(fā)測試過程中,可以直接將原有程序卸載就可以解決這個(gè)問題糕殉,但是本地之前存儲的數(shù)據(jù)也會消失亩鬼。如果是線上程序,就涉及到版本遷移的問題阿蝶,否則會導(dǎo)致崩潰雳锋,并提示如下錯(cuò)誤:

CoreData: error: Illegal attempt to save to a file that was never opened. "This NSPersistentStoreCoordinator has no persistent stores (unknown).  It cannot perform a save operation.". No last error recorded.

然而在需求不斷變化的過程中,后續(xù)版本肯定會對原有的模型文件進(jìn)行修改羡洁,這時(shí)就需要用到版本遷移的技術(shù)玷过,下面開始講版本遷移的方案。

創(chuàng)建新版本模型文件

本文中講的幾種版本遷移方案筑煮,在遷移之前都需要對原有的模型文件創(chuàng)建新版本辛蚊。

選中需要做遷移的模型文件 -> 點(diǎn)擊菜單欄Editor -> Add Model Version -> 選擇基于哪個(gè)版本的模型文件(一般都是選擇目前最新的版本),新建模型文件完成真仲。

對于新版本模型文件的命名袋马,我在創(chuàng)建新版本模型文件時(shí),一般會拿當(dāng)前工程版本號當(dāng)做后綴秸应,這樣在模型文件版本比較多的時(shí)候虑凛,就可以很容易將模型文件版本和工程版本對應(yīng)起來碑宴。

創(chuàng)建新版本模型文件

添加完成后,會發(fā)現(xiàn)之前的模型文件會變成一個(gè)文件夾桑谍,里面包含著多個(gè)模型文件延柠。

模型文件夾

在新建的模型文件中,里面的文件結(jié)構(gòu)和之前的文件結(jié)構(gòu)相同锣披。后續(xù)的修改都應(yīng)該在新的模型文件上捕仔,之前的模型文件不要再動(dòng)了,在修改完模型文件后盈罐,記得更新對應(yīng)的模型類文件榜跌。

基于新的模型文件,對Employee實(shí)體做如下修改盅粪,下面的版本遷移也以此為例钓葫。

修改之前

添加一個(gè)String類型的屬性,設(shè)置屬性名為sectionName票顾。

修改之后

此時(shí)還應(yīng)該選中模型文件础浮,設(shè)置當(dāng)前模型文件的版本。這里選擇將最新版本設(shè)置為剛才新建的1.1.0版本奠骄,模型文件設(shè)置工作完成豆同。

Show The File Inspector -> Model Version -> Current 設(shè)置為最新版本。
設(shè)置版本

對模型文件的設(shè)置已經(jīng)完成了含鳞,接下來系統(tǒng)還要知道我們想要怎樣遷移數(shù)據(jù)影锈。在遷移過程中可能會存在多種可能,蘋果將這個(gè)靈活性留給了我們完成蝉绷。剩下要做的就是編寫遷移方案以及細(xì)節(jié)的代碼鸭廷。

輕量級版本遷移

輕量級版本遷移方案非常簡單,大多數(shù)遷移工作都是由系統(tǒng)完成的熔吗,只需要告訴系統(tǒng)遷移方式即可辆床。在持久化存儲協(xié)調(diào)器(PSC)初始化對應(yīng)的持久化存儲(NSPersistentStore)對象時(shí),設(shè)置options參數(shù)即可桅狠,參數(shù)是一個(gè)字典讼载。PSC會根據(jù)傳入的字典,自動(dòng)推斷版本遷移的過程中跌。

字典中設(shè)置的key
  • NSMigratePersistentStoresAutomaticallyOption設(shè)置為YES咨堤,CoreData會試著把低版本的持久化存儲區(qū)遷移到最新版本的模型文件。

  • NSInferMappingModelAutomaticallyOption設(shè)置為YES晒他,CoreData會試著以最為合理地方式自動(dòng)推斷出源模型文件的實(shí)體中吱型,某個(gè)屬性到底對應(yīng)于目標(biāo)模型文件實(shí)體中的哪一個(gè)屬性逸贾。

版本遷移的設(shè)置是在創(chuàng)建MOC時(shí)給PSC設(shè)置的陨仅,為了使代碼更直觀津滞,下面只給出發(fā)生變化部分的代碼,其他MOC的初始化代碼都不變灼伤。

// 設(shè)置版本遷移方案
NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
                                NSInferMappingModelAutomaticallyOption : @YES};

// 創(chuàng)建持久化存儲協(xié)調(diào)器触徐,并將遷移方案的字典當(dāng)做參數(shù)傳入
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:options error:nil];
修改實(shí)體名

假設(shè)需要對已存在實(shí)體進(jìn)行改名操作,需要將重命名后的實(shí)體Renaming ID狐赡,設(shè)置為之前的實(shí)體名撞鹉。下面是Employee實(shí)體進(jìn)行操作。

修改實(shí)體名

修改后再使用實(shí)體時(shí)颖侄,應(yīng)該將實(shí)體名設(shè)為最新的實(shí)體名鸟雏,這里也就是Employee2,而且數(shù)據(jù)庫中的數(shù)據(jù)也會遷移到Employee2表中览祖。

Employee2 *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee2" inManagedObjectContext:context];
emp.name = @"lxz";
emp.brithday = [NSDate date];
emp.height = @1.9;
[context save:nil];

Mapping Model 遷移方案

輕量級遷移方案只是針對增加和改變實(shí)體孝鹊、屬性這樣的一些簡單操作,假設(shè)有更復(fù)雜的遷移需求展蒂,就應(yīng)該使用Xcode提供的遷移模板(Mapping Model)又活。通過Xcode創(chuàng)建一個(gè)后綴為.xcmappingmodel的文件,這個(gè)文件是專門用來進(jìn)行數(shù)據(jù)遷移用的锰悼,一些變化關(guān)系也會體現(xiàn)在模板中柳骄,看起來非常直觀

這里還以上面更改實(shí)體名箕般,并遷移實(shí)體數(shù)據(jù)為例子耐薯,將Employee實(shí)體遷移到Employee2中。首先將Employee實(shí)體改名為Employee2丝里,然后創(chuàng)建Mapping Model文件可柿。

Command + N 新建文件 -> 選擇 Mapping Model -> 選擇源文件 Source Model -> 選擇目標(biāo)文件 Target Model -> 命名 Mapping Model 文件名 -> Create 創(chuàng)建完成。
Mapping Model 文件

現(xiàn)在就創(chuàng)建好一個(gè)Mapping Model文件丙者,文件中顯示了實(shí)體复斥、屬性、Relationships械媒,源文件和目標(biāo)文件之間的關(guān)系目锭。實(shí)體命名是EntityToEntity的方式命名的,實(shí)體包含的屬性和關(guān)聯(lián)關(guān)系纷捞,都會被添加到遷移方案中(Entity Mapping痢虹,Attribute MappingRelationship Mapping)主儡。

在遷移文件的下方是源文件和目標(biāo)文件的關(guān)系奖唯。

對應(yīng)關(guān)系

在上面圖中改名后的Employee2實(shí)體并沒有遷移關(guān)系,由于是改名后的實(shí)體糜值,系統(tǒng)還不知道實(shí)體應(yīng)該怎樣做遷移丰捷。所以選中Mapping Model文件的Employee2 Mappings坯墨,可以看到右側(cè)邊欄的Sourceinvalid value。因?yàn)橐獜?code>Employee實(shí)體遷移數(shù)據(jù)過來病往,所以將其選擇為Employee捣染,遷移關(guān)系就設(shè)置完成了。

設(shè)置完成后停巷,還應(yīng)該將之前EmployeeToEmployeeMappings刪除耍攘,因?yàn)檫@個(gè)實(shí)體已經(jīng)被Employee2替代,它的Mappings也被Employee2 Mappings所替代畔勤,否則會報(bào)錯(cuò)蕾各。

設(shè)置遷移關(guān)系

在實(shí)體的遷移過程中,還可以通過設(shè)置Predicate的方式庆揪,來簡單的控制遷移過程示损。例如只需要遷移一部分指定的數(shù)據(jù),就可以通過Predicate來指定嚷硫〖旆茫可以直接在右側(cè)Filter Predicate的位置設(shè)置過濾條件,格式是$source.height < 100仔掸,$source代表數(shù)據(jù)源的實(shí)體脆贵。

Filter Predicate

更復(fù)雜的遷移需求

如果還存在更復(fù)雜的遷移需求,而且上面的遷移方式不能滿足起暮,可以考慮更復(fù)雜的遷移方式卖氨。假設(shè)要在遷移過程中,對遷移的數(shù)據(jù)進(jìn)行更改负懦,這時(shí)候上面的遷移方案就不能滿足需求了筒捺。

對于上面提到的問題,在Mapping Model文件中選中實(shí)體纸厉,可以看到Custom Policy這個(gè)選項(xiàng)系吭,選項(xiàng)對應(yīng)的是NSEntityMigrationPolicy的子類,可以創(chuàng)建并設(shè)置一個(gè)子類颗品,并重寫這個(gè)類的方法來控制遷移過程肯尺。

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error;

版本遷移總結(jié)

版本遷移在需求的變更中肯定是要發(fā)生的,但是我們應(yīng)該盡量避免這樣的情況發(fā)生躯枢。在最開始設(shè)計(jì)模型文件數(shù)據(jù)結(jié)構(gòu)的時(shí)候则吟,就應(yīng)該設(shè)計(jì)一個(gè)比較完善并且容易應(yīng)對變化的結(jié)構(gòu),這樣后面就算發(fā)生變化也不會對結(jié)構(gòu)主體造成大的改動(dòng)锄蹂。


好多同學(xué)都問我有Demo沒有氓仲,其實(shí)文章中貼出的代碼組合起來就是個(gè)Demo。后來想了想,還是給本系列文章配了一個(gè)簡單的Demo敬扛,方便大家運(yùn)行調(diào)試晰洒,后續(xù)會給所有博客的文章都加上Demo

Demo只是來輔助讀者更好的理解文章中的內(nèi)容舔哪,應(yīng)該博客結(jié)合Demo一起學(xué)習(xí)欢顷,只看Demo還是不能理解更深層的原理槽棍。Demo中幾乎每一行代碼都會有注釋捉蚤,各位可以打斷點(diǎn)跟著Demo執(zhí)行流程走一遍,看看各個(gè)階段變量的值炼七。

Demo地址劉小壯的Github


這兩天更新了一下文章缆巧,將CoreData系列的六篇文章整合在一起,做了一個(gè)PDF版的《CoreData Book》豌拙,放在我Github上了陕悬。PDF上有文章目錄,方便閱讀按傅。

如果你覺得不錯(cuò)捉超,請把PDF幫忙轉(zhuǎn)到其他群里,或者你的朋友唯绍,讓更多的人了解CoreData拼岳,衷心感謝!??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末况芒,一起剝皮案震驚了整個(gè)濱河市惜纸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绝骚,老刑警劉巖耐版,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異压汪,居然都是意外死亡粪牲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門止剖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虑瀑,“玉大人,你說我怎么就攤上這事滴须∩喙罚” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵扔水,是天一觀的道長痛侍。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么主届? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任赵哲,我火速辦了婚禮,結(jié)果婚禮上君丁,老公的妹妹穿的比我還像新娘枫夺。我一直安慰自己,他們只是感情好绘闷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布橡庞。 她就那樣靜靜地躺著,像睡著了一般印蔗。 火紅的嫁衣襯著肌膚如雪扒最。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天华嘹,我揣著相機(jī)與錄音吧趣,去河邊找鬼。 笑死耙厚,一個(gè)胖子當(dāng)著我的面吹牛强挫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播薛躬,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼俯渤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泛豪?” 一聲冷哼從身側(cè)響起稠诲,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诡曙,沒想到半個(gè)月后臀叙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡价卤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年劝萤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慎璧。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡床嫌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胸私,到底是詐尸還是另有隱情厌处,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布岁疼,位于F島的核電站阔涉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瑰排,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一贯要、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧椭住,春花似錦崇渗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至傻挂,卻和暖如春乘碑,著一層夾襖步出監(jiān)牢的瞬間挖息,已是汗流浹背金拒。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓柔滔,卻偏偏與公主長得像晌块,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子但绕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容