「死磕」Core Data——入門

適讀對象:

  • 需要入門Core Data的朋友;
  • 像我一樣,尚未學(xué)過數(shù)據(jù)庫相關(guān)課程,不太懂怎么寫SQLite語句的朋友;
  • 想了解一個(gè)文科狗理解、學(xué)習(xí)Core Data心路歷程的朋友~

Core Data,iOS中一種保存和讀取數(shù)據(jù)的機(jī)制。以學(xué)習(xí)曲線陡峭而聞名~

因?yàn)槲沂俏目乒忿D(zhuǎn)行的程序猿,并沒有學(xué)過數(shù)據(jù)庫相關(guān)課程,也欣賞不出SQLite的美,所以之前的項(xiàng)目一直用NSKeyedArchiverNSKeyedUnarchiver(固化)進(jìn)行數(shù)據(jù)的本地保存(所幸我接觸的項(xiàng)目,數(shù)據(jù)都不會(huì)太復(fù)雜)。

其實(shí)一開始接觸iOS開發(fā),就有閱讀過Core Data相關(guān)內(nèi)容。不過一來當(dāng)時(shí)水平太低,看不太懂;二來Core Data本來也難學(xué);三來經(jīng)手的項(xiàng)目也沒有強(qiáng)制使用Core Data;四來國內(nèi)使用Core Data的開發(fā)者也不占主流。所以花了很長很長一段時(shí)間才入了門。過程算是曲折,所以標(biāo)題嘩眾取寵地用了「死磕」二字。

「太長不看版」

本文確實(shí)比較長(從側(cè)面印證Core Data內(nèi)容確實(shí)多),所以這里寫一個(gè)「太長不看版」,「以饗讀者」:

Core Data使用流程:

  • 創(chuàng)建Core Data Stack
  • iOS10中利用NSPersistentContainer
  • iOS10之前涉及NSManagedObjectContextNSPersistentStoreCoordinatorNSManagedObjectModelNSPersistentStore這些類
  • 創(chuàng)建模型文件(.xcdatamodel文件)
  • 按需增加「實(shí)體」、實(shí)體的「特性」、「關(guān)系」(如有需要)
  • 創(chuàng)建NSManagedObject子類(如有需要)

備注:如果創(chuàng)建項(xiàng)目時(shí)勾選了「Use Core Data」,會(huì)自動(dòng)幫你創(chuàng)建好上述這些內(nèi)容。

  • 增刪查改:
  • :利用NSEntityDescription的類方法insertNewObjectForEntityForName:inManagedObjectContext:,添加數(shù)據(jù)(對象)
  • :利用NSManagedObjectContextdeleteObject:方法刪除數(shù)據(jù)
  • :利用NSManagedObjectContextexecuteFetchRequest:error:方法,查詢數(shù)據(jù)
  • 保存:利用NSManagedObjectContextsave:方法保存數(shù)據(jù)。

OK,基本上就是這些東西了~

術(shù)語

CoreData學(xué)習(xí)曲線陡峭的原因之一,術(shù)語太多算一個(gè)。所以這里整理一下,如下:

iOS Core Data 示意圖
iOS Core Data 示意圖

Core Data Stack

感覺理解起來有點(diǎn)抽象,先看官方定義:

The Core Data stack is a collection of framework objects that are accessed as part of the initialization of Core Data and that mediate between the objects in your application and external data stores.

說是一個(gè)對象的集合,由4個(gè)主要對象構(gòu)成:

  • 「managed object context」 (NSManagedObjectContext),
  • 「persistent store coordinator」 (NSPersistentStoreCoordinator),
  • 「managed object model」 (NSManagedObjectModel),
  • 「persistent container」 (NSPersistentContainer).

我是這樣理解的:Core Data Stack,就是進(jìn)行數(shù)據(jù)增刪查改、保存的「工作臺」,Apple提供這樣一個(gè)「工作臺」,讓你方便進(jìn)行數(shù)據(jù)的保存。無需關(guān)心實(shí)現(xiàn)細(xì)節(jié)。

對應(yīng)示意圖第1個(gè)框框。

Persistent Container

NSPersistentContainer是iOS 10、 macOS 10.12之后才出現(xiàn)的新類。引入這個(gè)新類的目的之一,就是為了簡化創(chuàng)建Core Data Stack這個(gè)工作臺的過程。所以,在iOS10之前,創(chuàng)建Core Data Stack會(huì)復(fù)雜一些。

而Persistent Container也有另一個(gè)新類NSPersistentStoreDescription,可以利用這個(gè)類,進(jìn)行一些定制化設(shè)置,比如自定義存儲(chǔ)路徑、設(shè)置存儲(chǔ)數(shù)據(jù)方式等(Core Data支持SQLite、XML、Binary、InMemory 4中方式存儲(chǔ)數(shù)據(jù))。

備注:iOS10中,如果利用NSPersistentContainer創(chuàng)建Core Data Stack,預(yù)設(shè)的是NSSQLiteStoreType類型。并且默認(rèn)打開了自動(dòng)輕量化版本遷移功能(換言之,在iOS10之前,需要手動(dòng)進(jìn)行相關(guān)設(shè)置,才能打開版本遷移功能)。

對應(yīng)示意圖第2個(gè)虛線框框。

Managed Object Context。

可以理解為是一塊內(nèi)存,提供了和Managed Objects交互的場所。也稱為:The Context或者M(jìn)OC。NSManagedObjectContext類實(shí)例。

備注:對數(shù)據(jù)進(jìn)行刪除、保存、查詢,都要用到NSManagedObjectContext類的相關(guān)方法。

對應(yīng)示意圖第3個(gè)框框。

Managed Object Model

直觀點(diǎn),你可以把它理解為就是Xcode中后綴為xcdatamodel的文件。在這個(gè)文件里,你可以通過非代碼、可視化的方式,定義對象、對象的屬性、對象之間的關(guān)系(Core Data把對象稱呼為「實(shí)體」、對象的屬性稱呼為「特性」)。

Managed Object Model,就是Core Data中用于描述實(shí)體、實(shí)體特性、實(shí)體間關(guān)系的一套方案。

它是NSManagenObjectModel的類實(shí)例(也可以通過純代碼實(shí)現(xiàn).xcdatamodel文件的內(nèi)容)。也稱為:The Model, Data Model, Schema或Object Graph。

換言之,Managed Object Model定義了你App的整個(gè)數(shù)據(jù)結(jié)構(gòu)。

下面3個(gè),是在設(shè)置.xcdatamodel文件時(shí)會(huì)遇到的3個(gè)術(shù)語。

  • Entity /「實(shí)體」

NSEntityDescription類實(shí)例,用于定義一個(gè)對象。一個(gè)「實(shí)體」,最少要有「名字」和「類名」(如果沒有設(shè)置類名,默認(rèn)是NSManagedObject類)。

  • Attribute / 特性

「實(shí)體特性」。NSAttributeDescription類實(shí)例。就是Entity的特性,對應(yīng)App中的創(chuàng)建類時(shí)的屬性。

  • Relationship / 關(guān)系

「實(shí)體關(guān)系」。 NSRelationshipDescription類實(shí)例。用于描述Entity之間的關(guān)系。

對應(yīng)示意圖第4個(gè)框框。

Managed Object。

就是需要保存的數(shù)據(jù),是NSManagenObject類實(shí)例。(對應(yīng)App中的「對象」)

就我的理解,Managed Object和上面提到的Entity,本質(zhì)上是同一個(gè)東西,就是你的數(shù)據(jù)對象,只不過是在可視化操作和純代碼操作中的不同稱謂。

對應(yīng)示意圖第5的那些框框。

Persistent Store Coordinator

協(xié)調(diào)Context和Persistent Store的一個(gè)角色。NSPersistentStoreCoordinator類實(shí)例。

如果只是對數(shù)據(jù)進(jìn)行簡單的增刪查改,我們并不需要接觸到這個(gè)類。

對應(yīng)示意圖第6個(gè)框框。

Persistent Store

可以理解為保存數(shù)據(jù)的地方。用于設(shè)置保存數(shù)據(jù)的方式、以及保存的路徑等。(保存數(shù)據(jù)的方式指SQLite、XML、Binary、InMemory4種)。NSPersistentStore類實(shí)例。也稱為The Store或者Database。

在iOS10之前,如果需要支持版本遷移功能,需要在創(chuàng)建NSPersistentStore類實(shí)例時(shí),傳入相應(yīng)的options參數(shù)。而在iOS10中,則會(huì)自動(dòng)打開版本遷移功能,并默認(rèn)設(shè)置數(shù)據(jù)類型為NSSQLiteStoreType(見上面的名詞:「NSPersistentStoreDescription」)。

「版本遷移」,一開始對這個(gè)名字很是迷惑,還以為是將數(shù)據(jù)模型從一個(gè)App遷移到另外一個(gè)App。其實(shí),是在內(nèi)部進(jìn)行「遷移」。

簡單說,假如修改了數(shù)據(jù)模型(比如修改了. xcdatamodel文件:增加了實(shí)體,增加了特性等等),為了防止使用者在更新App后,由于數(shù)據(jù)模型不一致導(dǎo)致崩潰,需要進(jìn)行一定的處理,這個(gè)處理,他們叫「版本遷移」(叫「版本升級」不是更合適嗎~)。

對應(yīng)示意圖第7個(gè)框框。

其他

  • Optional:「實(shí)體特性」的配置選項(xiàng)(勾選了之后,表示這個(gè)特征可為空nil)

  • Transient:「實(shí)體特性」的配置選項(xiàng)(勾選了之后,該屬性不會(huì)保存到沙盒中)

  • Fetch Requset。描述了從Persistent Store中取回?cái)?shù)據(jù)的方式方法。NSFetchRequest類實(shí)例。查詢數(shù)據(jù)的時(shí)候會(huì)用到。

  • Preficate:又稱為:Filter。描述了取回?cái)?shù)據(jù)的過濾方式。(有人翻譯為「斷言」,有人翻譯為「謂語」)。NSPredicate類實(shí)例。查詢數(shù)據(jù)的時(shí)候會(huì)用到。

  • Stort Descriptor。描述了取回?cái)?shù)據(jù)的排序方式。NSSortDescriptor類實(shí)例。也是查詢數(shù)據(jù)的時(shí)候會(huì)用到。

可參考以下表格,對照進(jìn)行理解(這個(gè)表格或許不慎嚴(yán)謹(jǐn))

數(shù)據(jù)庫術(shù)語 代碼中的術(shù)語 Core Data中的術(shù)語
表格 實(shí)體 / Entity(NSEntityDescription類實(shí)例)
屬性 實(shí)體特性(Attribute)
對象(類實(shí)例) NSManagedObject(子)類實(shí)例

使用步驟

大部分教程是先創(chuàng)建「managed object model」,再初始化「Core Data Stack」的。因?yàn)槲疫@里把「Core Data Stack」比喻成「工作臺」,所以這篇文章先進(jìn)行「Core Data Stack」的初始化。

1、初始化Core Data Stack

上面我們將Core Data Stack比喻成一個(gè)「工作臺」,是一切操作的所在地。

不過由于iOS10新引進(jìn)了NSPersistentContainer類,然后新建項(xiàng)目又可以選擇勾選Core Data與否。所以情況變得稍稍有點(diǎn)復(fù)雜。

這里分三種情況:1、在既有項(xiàng)目(只需支持iOS10)初始化Core Data Stack;2、在既有項(xiàng)目(需兼容iOS8、9、10等系統(tǒng))初始化Core Data Stack;3、新建項(xiàng)目時(shí)直接勾選了Core Data。

情況1:在既有項(xiàng)目添加Core Data功能(只需支持iOS10)

由于iOS10引進(jìn)了NSPersistentContainer,如果單單只支持iOS10系統(tǒng),初始化Core Data Stack相比以前簡單很多。

// 我們先聲明了一個(gè)NSPersistentContainer類型的屬性:persistentContainer,在適合的時(shí)間調(diào)用initWithName:對其初始化
// 這里的Name參數(shù),需要和后續(xù)創(chuàng)建的.xcdatamodeld模型文件名稱一致。
_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"MoveBand"];
    
// 調(diào)用loadPersistentStoresWithCompletionHandler:方法,完成Core Data Stack的最中初始化。
// 如果不能初始化成功,在Block回調(diào)中打印錯(cuò)誤,方便調(diào)試
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription * _Nonnull description, NSError * _Nullable error) {
        
    if (error != nil) {
        NSLog(@"Fail to load Core Data Stack : %@", error);
        abort();
    }
    else {
        ...
    }
}];

就兩步:

  • 初始化NSPersistentContainer對象;
  • 調(diào)用NSPersistentContainer的loadPersistentStoresWithCompletionHandler:完成初始化。

更詳細(xì)的說明,可參考官方文檔Initializing the Core Data Stack

備注:你可以仿照Xcode所創(chuàng)建的模版,直接在AppDelegate類中橋敲以上代碼。也可以新建一個(gè)專門負(fù)責(zé)儲(chǔ)存功能的類,在這個(gè)類中敲這段代碼。(我一般不喜歡將這部分代碼放在AppDelegate類中,所以我創(chuàng)建工程的時(shí)候,都不會(huì)勾選Use Core Data)。

情況2:在既有項(xiàng)目初始化Core Data Stack(需兼容iOS8、9、10等系統(tǒng))

因?yàn)?strong>NSPersistentContainer不兼容iOS10之前的系統(tǒng)。所以,如果你已經(jīng)用了NSPersistentContainer初始化了Core Data Stack,但同時(shí)也要兼容iOS8、9等系統(tǒng),就需要在代碼中檢查,如果是舊的系統(tǒng),就需要用舊的方法初始化Core Data Stack了。示例如下:


- (instancetype)init
{
    self = [super init];
    if (self) {
        NSInteger majorVersion = [NSProcessInfo processInfo].operatingSystemVersion.majorVersion;
        
        if (majorVersion < 10) {
            // iOS10以下的系統(tǒng), 用舊有的方法初始化Core Data Stack
            [self initializeCoreDataLessThaniOS10];
        }
        else {
            // iOS10的系統(tǒng), 用新的方法(詳見上面介紹的情況1)
            [self initializeCoreData];
        }
    }
    return self;
}


- (void)initializeCoreDataLessThaniOS10 {
    // Get managed object model(拿到模型文件,也就是.xcdatamodeld文件(我們會(huì)在初始化完Core data Stack后創(chuàng)建))
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MoveBand" withExtension:@"momd"];
    NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    NSAssert(mom != nil, @"Error initalizing Managed Object Model");
    
    // Create persistent store coordinator(創(chuàng)建NSPersistentStoreCoordinator對象(需要傳入上述創(chuàng)建的NSManagedObjectModel對象))
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
    
    // Creat managed object context(創(chuàng)建NSManagedObjectContext對象(_context是聲明在.h文件的屬性——因?yàn)槠渌愐惨玫竭@個(gè)屬性))
    _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    
    // assgin persistent store coordinator(賦值persistentStoreCoordinator)
    _context.persistentStoreCoordinator = psc;
    
    // Create .sqlite file(在沙盒中創(chuàng)建.sqlite文件)
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"DataModel.sqlite"];
    
    // Create persistent store(異步創(chuàng)建NSPersistentStore并add到NSPersistentStoreCoordinator對象中,作用是設(shè)置保存的數(shù)據(jù)類型(NSSQLiteStoreType)、保存路徑、是否支持版本遷移等)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // 用于支持版本遷移的參數(shù)
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
        NSError *error = nil;
        NSPersistentStoreCoordinator *psc = _context.persistentStoreCoordinator;
        
        // 備注,如果options參數(shù)傳nil,表示不支持版本遷移
        NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType
                                                     configuration:nil
                                                               URL:storeURL
                                                           options:options
                                                             error:&error];
        NSAssert(store != nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
    });
}

可以看到,舊方法初始化Core Data Stack還是比較麻煩的。

當(dāng)然,如果你不想做這個(gè)判斷,只用上面方法初始化即可,這個(gè)方法在新舊系統(tǒng)都正常工作。

情況3:直接勾選Core Data

創(chuàng)建項(xiàng)目時(shí),如果直接勾選Core Data復(fù)選框,項(xiàng)目模版會(huì)在AppDelegate類中直接幫你初始化好Core Data Stack,自動(dòng)創(chuàng)建和上面情況1類似的代碼(Xcode8)

在AppDelegate.h文件

#import <UIKit/UIKit.h>
// 導(dǎo)入了CoreData框架
#import <CoreData/CoreData.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

// 在.h文件聲明一個(gè)NSPersistentContainer類型的屬性(為了讓其他類可以調(diào)用)
@property (readonly, strong) NSPersistentContainer *persistentContainer;

// 聲明了一個(gè)保存數(shù)據(jù)的方法
- (void)saveContext;

@end

在AppDelegate.m文件

@implementation AppDelegate

……

#pragma mark - Core Data stack

@synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    @synchronized (self) {
        if (_persistentContainer == nil) {
            // 實(shí)例化NSPersistentContainer對象。
            // 注意:參數(shù)傳入的名稱,就是.xcdatamodeld文件名稱(兩者需要一直)(勾選Core Data后,會(huì)自動(dòng)創(chuàng)建一個(gè).xcdatamodeld文件)
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataTestUseCoreData"];
            
            // 加載persistent stores,實(shí)現(xiàn)最終的Core Data stack的創(chuàng)建
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                // 如果有錯(cuò)誤,打印出來
                if (error != nil) {
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

#pragma mark - Core Data Saving support

- (void)saveContext {
    // 注意這句,NSManagedObjectContext對象,是通過上面創(chuàng)建的NSPersistentContainer對象的屬性viewContext獲取的,無需自己初始化(iOS10之前要自己初始化)
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    
    NSError *error = nil;
    // 保存數(shù)據(jù),直接用的是NSManagedObjectContext的save:方法,很簡單。
    if ([context hasChanges] && ![context save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, error.userInfo);
        abort();
    }
}

系統(tǒng)幫我們創(chuàng)建了一個(gè)NSPersistentContainer實(shí)例,以及一個(gè)saveContext方法。(并且已經(jīng)幫我們創(chuàng)建了.xcdatamodeld模型文件)

注意看saveContext,我們通過NSPersistentContainer的屬性viewContext拿到NSManagedObjectContext對象,再通過save:方法進(jìn)行數(shù)據(jù)的保存。

因?yàn)橄到y(tǒng)并沒有幫我們適配舊系統(tǒng),所以如果App要在非iOS10的舊系統(tǒng)運(yùn)行,還需要做類似情況2的工作。

如果是Xcode8之前的版本自動(dòng)創(chuàng)建的Core Data Stack,會(huì)不一樣(跟情況2類似),這里不再贅述。

2、創(chuàng)建「managed object model」

好了,有了「工作臺」,接著就需要「材料」了。也就是你要保存什么東西,這些東西有什么特性,這些東西之間有什么關(guān)系……Xcode提供了一套可視化的方案讓我們「描述」這部分內(nèi)容。

創(chuàng)建xcdatamodeld文件

快捷鍵:Command + N,選擇Core Data欄目下的「Data Model」,就可以創(chuàng)建一個(gè).xcdatamodeld模型文件(managed object model),名字隨意。接著我們就可以往里面添加材料了。

添加實(shí)體、實(shí)體的特性、關(guān)系

這部分用一張圖概括:


添加實(shí)體、實(shí)體的特性、關(guān)系示意圖
添加實(shí)體、實(shí)體的特性、關(guān)系示意圖

:這里有個(gè)坑,在Xcode8中,Codegen下拉選擇框中增加了Class/Definition這一選項(xiàng),而且是默認(rèn)的預(yù)設(shè)值,這時(shí)候系統(tǒng)會(huì)自動(dòng)幫我們這個(gè)實(shí)體創(chuàng)建了NSManagedObject子類,最坑的是,這些自動(dòng)創(chuàng)建的類,在導(dǎo)航面板是看不見的!!!然后你很容易再重復(fù)手動(dòng)創(chuàng)建NSManagedObject子類,這時(shí)候就會(huì)報(bào)類似「duplicate symbol _OBJC_METACLASS_Photography in:...」這類錯(cuò)誤。

所以,如果你想自己手動(dòng)創(chuàng)建NSManagedObject子類,就要把系統(tǒng)預(yù)設(shè)的Class/Definition改為Manual/None

創(chuàng)建NSManagedObject子類

好了,通過上面的一步,我們知道我們要保存的是什么東西,以及知道他們是什么關(guān)系了(數(shù)據(jù)模型建好了)。

為什么要用NSManagedObject子類

這時(shí)候其實(shí)可以進(jìn)行數(shù)據(jù)的增刪查改了。但是這時(shí)候賦值(或者修改)一條數(shù)據(jù),都是通過NSManagedObject類實(shí)例進(jìn)行的(我們創(chuàng)建的實(shí)體,都是NSManagedObject類型的),類似如下:

NSManagedObject *newUser = …… // 這里聚焦在數(shù)據(jù)的賦值與取值, 暫時(shí)省略插入一條數(shù)據(jù)的方法

// 賦值
[newUser setValue:@"Antony" forKey:@"name"];
[newUser setValue:@123 forKey:@"userID"];

// 取值
NSManagedObject *selectedUser = ……
NSString *name = [selectedUser valueForKey@"name"];
……

以上的存取值方式,有點(diǎn)類似字典。不直觀,敲字符串也容易出錯(cuò)。所以,我們通常都會(huì)創(chuàng)建NSManagedObject的子類,用點(diǎn)語法直接進(jìn)行存取操作。

在.h文件

#import <CoreData/CoreData.h>

@interface SPKUser : NSManagedObject

@property (copy, nonatomic) NSString *name;

@property (nonatomic) int64_t userID;

@end

在.m文件

#import "SPKUser.h"

@implementation SPKUser

// 在OC中,將某個(gè)屬性實(shí)現(xiàn)為@dynamic,表示編譯器在編譯時(shí)不會(huì)對這個(gè)屬性的存取方法(getter/setter)做檢查(由程序員自己提供存取方法)。在Core Data中,由Core Data實(shí)現(xiàn)。
@dynamic name;
@dynamic userID;

@end

然后就可以這樣:

- (void)addNewUser {
    SPKUser *newUser = ……;
    
    newUser.name = @"Antony";
    newUser.userID = 123;
    NSLog(@"添加了一個(gè)user");
}

所以,這就是應(yīng)用NSManagedObject子類的好處。

如何創(chuàng)建NSManagedObject子類

創(chuàng)建NSManagedObject子類,有如下兩種辦法

  • 方法1:直接Command + N創(chuàng)建一個(gè)新類,繼承NSManagedObject類,然后定義的屬性和模型文件中的一致。
  • 方法2:選中對應(yīng)的實(shí)體,然后Editor > Create NSManagedObject Subclass...,系統(tǒng)自動(dòng)生成NSManagedObject子類。
    這種方法,如果有「對多」的關(guān)系,會(huì)生成2個(gè)Category(Core Data生成的NSManagedObject子類,都是以Category形式存在的)
  • CoreDataProperties:生成實(shí)體中Attributes對應(yīng)的屬性。Relationships也會(huì)生成對應(yīng)的屬性:「對多」關(guān)系是NSSet/NSOrderSet類型屬性(本質(zhì)是個(gè)集合),「對一」關(guān)系則是非集合的對象類型屬性。
  • CoreDataGeneratedAccessors——其實(shí)就是一系列增加、刪除NSOrderSet/NSSet里元素的方法。(如果沒有對多關(guān)系,不會(huì)有這個(gè)Category)

注意,第二種方式創(chuàng)建NSManagedObject子類,默認(rèn)語言是Swift,如果需要改為OC,則到「File inspector」中修改,如下:

修改創(chuàng)建NSManagedObject子類的語言
修改創(chuàng)建NSManagedObject子類的語言

3、增

好啦,有了「工作臺」(Core Data Stack),又有了「材料」(managed object model),可以擼起袖子干了……(第一張示意圖,其實(shí)都有對增刪查、保存方法有所提及)

- (void)addNewUser {
    SPKUser *newUser = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:_context];
    
    newUser.name = @"Antony";
    newUser.userID = 123;
}

看以上代碼,增加一條數(shù)據(jù),并不是調(diào)用NSManagedObjectContext類中的某個(gè)方法,而是用了NSEntityDescription的類方法insertNewObjectForEntityForName:inManagedObjectContext:,第一個(gè)參數(shù)傳入實(shí)體名稱,第二個(gè)參數(shù)傳入context(因?yàn)镃ore Data是支持多個(gè)context的,所以這里傳入context參數(shù)以界定是在哪個(gè)context中操作)。

該方法會(huì)返回一個(gè)NSManagedObject,或其子類的對象,然后就可以對該對象進(jìn)行賦值操作了。

注意:此時(shí)數(shù)據(jù)只存在內(nèi)存中,并沒有固化、保存到沙盒。還需要通過特定的保存方法才能固化到沙盒。

另外,不能用alloc、init方法創(chuàng)建一個(gè)新的對象,會(huì)崩潰。

4、刪

刪除數(shù)據(jù)比較簡單,直接調(diào)用NSManagedObjectContextdeleteObject:方法即可。當(dāng)然,要怎么獲取所要?jiǎng)h除的對象,就自己斟酌了,可以通過NSFetchRequest查詢獲取要?jiǎng)h除的對象,也可以用NSFetchedResultsController的objectAtIndexPath:方法拿到要?jiǎng)h除的對象(NSFetchedResultsController另一篇文章再介紹)

- (void)removeUser:(SPKUser *)user {
    [_context deleteObject:user];
}

5、查(Fetching Objects)

查詢功能,是被官方特別強(qiáng)調(diào)的一個(gè)功能,據(jù)聞可以玩出很多花樣兒~

- (NSArray *)allUsers {
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    NSError *error = nil;
    
    NSArray *results = [_context executeFetchRequest:request error:&error];

    if (!results) {
        NSLog(@"Error fetching Employee objects: %@\n%@", [error localizedDescription], [error userInfo]);
        abort();
    }
    
    return results;
}

上面是一個(gè)最簡單的查詢,調(diào)用NSManagedObjectContextexecuteFetchRequest:error:方法,傳入一個(gè)NSFetchRequest對象作為參數(shù),這個(gè)參數(shù)定義了要取回的是哪個(gè)實(shí)體。

另外,還可以通過NSPredicate(「謂語」,也有翻譯為「斷言」的)進(jìn)行數(shù)據(jù)篩選,只獲取某些符合條件的數(shù)據(jù)。還可以通過NSSortDescriptor設(shè)置獲取數(shù)據(jù)的排列順序。如下:

- (NSArray *)allUsers {
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 只取回firstName是Antony的數(shù)據(jù)
    NSString *firstName = @"Antony";
    [request setPredicate:[NSPredicate predicateWithFormat:@"firstName == %@", firstName]];
    
    // 取回的數(shù)據(jù)按userID進(jìn)行由小到大(升序)的排序
    NSSortDescriptor *userIDSort = [NSSortDescriptor sortDescriptorWithKey:@"userID" ascending:YES];
    
    // 注意,這個(gè)參數(shù)是一個(gè)數(shù)組,所以排序可以有多個(gè)條件,比如先按身高從低到高排,滿足此條件后再按照名字首字母A~Z從前到后排。這時(shí)候,身高的Sort Descriptor放在數(shù)組前面,名字的Sort Descriptor放在數(shù)組后面。
    [request setSortDescriptors:@[userIDSort]];
    
    NSError *error = nil;
    
    NSArray *results = [_context executeFetchRequest:request error:&error];

    if (!results) {
        NSLog(@"Error fetching Employee objects: %@\n%@", [error localizedDescription], [error userInfo]);
        abort();
    }
    
    return results;
}

關(guān)于NSPredicate更詳細(xì)的用法,可參考官方文檔:Predicate Programming Guide

6、改

修改數(shù)據(jù),和上面的增加一條數(shù)據(jù)的情況比較相似,直接對屬性進(jìn)行修改。先查詢到你要的數(shù)據(jù)對象,再重新賦值即可。

如果要大批量修改數(shù)據(jù),將數(shù)據(jù)從沙盒加載到內(nèi)存,再進(jìn)行修改,不利于性能,所以可以使用NSBatchUpdateRequestNSBatchDeleteRequest,進(jìn)行批量的修改或者刪除。這種方法直接在數(shù)據(jù)庫內(nèi)完成,無需加載到內(nèi)存,利于性能提升。(但進(jìn)行批處理后,因?yàn)椴僮魇窃跀?shù)據(jù)庫中完成的,要注意合并更新到Context中,以保持兩者一致)

關(guān)于批處理,可以參考《New in Core Data and iOS 8: Batch Updating》,這里不再展開( 其實(shí)我自己暫時(shí)也沒用過:D )

7、保存

保存比較簡單,直接調(diào)用NSManagedObjectContext的save:方法即可,如下:

- (void)save {
    NSError *error = nil;
    if ([_context save:&error] == NO) {
        NSAssert(NO, @"Error saving context %@\n%@", [error localizedDescription], [error userInfo]);
    }
}

也可以調(diào)用NSManagedObjectContexthasChanges方法,來判斷:在數(shù)據(jù)有變化的情況下再調(diào)用save:方法。

注意:在調(diào)用save方法之前,上面做的所有操作(增、刪、改),都只是保存在內(nèi)存中,并不會(huì)固化到沙盒中。

版本「遷移」

應(yīng)用場景:修改了數(shù)據(jù)結(jié)構(gòu)(比如說某個(gè)實(shí)體增加了一個(gè)特性),這時(shí)候就要進(jìn)行版本遷移了,否則已經(jīng)安裝舊App的手機(jī),在更新應(yīng)用后,兩邊數(shù)據(jù)結(jié)構(gòu)不一致導(dǎo)致不能識別,會(huì)崩潰。

步驟:

  • 選中.xcdatamodeld文件,Editor > Add Model Version,創(chuàng)建一個(gè)新版的.xcdatamodeld文件
  • 切換到新版的.xcdatamodeld文件(切換成功后會(huì)有綠色的勾),如下圖:


    切換到新版的.xcdatamodeld文件
    切換到新版的.xcdatamodeld文件
  • 對.xcdatamodeld文件進(jìn)行你想要的修改
  • 創(chuàng)建NSPersistentStore的時(shí)候,options參數(shù)傳一個(gè)dictionary,值如下:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

更詳細(xì)的代碼,上面用舊方法創(chuàng)建Core Data Stack時(shí)也有涉及。

大家也可以自己驗(yàn)證一下,不進(jìn)行版本遷移,直接修改.xcdatamodeld文件,然后運(yùn)行程序,會(huì)報(bào)什么錯(cuò)。

以上是自動(dòng)、輕量化的版本遷移,至于更復(fù)雜的版本遷移,我目前也沒有接觸到,不再展開。可以參考:

自定義 Core Data 遷移

Core Data Model Versioning and Data Migration Programming Guide

End

認(rèn)識CoreData-初識CoreData》系列文章,寫得很詳細(xì),推薦閱讀。

以上就是Core Data的入門用法(文科狗,不容易啊 XD )。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

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