「死磕」Core Data——入門

適讀對象:

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

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

因為我是文科狗轉(zhuǎn)行的程序猿,并沒有學(xué)過數(shù)據(jù)庫相關(guān)課程,也欣賞不出SQLite的美,所以之前的項目一直用NSKeyedArchiverNSKeyedUnarchiver(固化)進行數(shù)據(jù)的本地保存(所幸我接觸的項目,數(shù)據(jù)都不會太復(fù)雜)。

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

「太長不看版」

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

Core Data使用流程:

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

備注:如果創(chuàng)建項目時勾選了「Use Core Data」,會自動幫你創(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ù)語太多算一個。所以這里整理一下,如下:

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

Core Data Stack

感覺理解起來有點抽象,先看官方定義:

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.

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

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

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

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

Persistent Container

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

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

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

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

Managed Object Context。

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

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

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

Managed Object Model

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

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

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

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

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

  • Entity /「實體」

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

  • Attribute / 特性

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

  • Relationship / 關(guān)系

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

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

Managed Object。

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

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

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

Persistent Store Coordinator

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

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

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

Persistent Store

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

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

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

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

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

其他

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

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

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

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

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

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

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

使用步驟

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

1、初始化Core Data Stack

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

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

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

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

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

// 我們先聲明了一個NSPersistentContainer類型的屬性:persistentContainer,在適合的時間調(diào)用initWithName:對其初始化
// 這里的Name參數(shù),需要和后續(xù)創(chuàng)建的.xcdatamodeld模型文件名稱一致。
_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"MoveBand"];
    
// 調(diào)用loadPersistentStoresWithCompletionHandler:方法,完成Core Data Stack的最中初始化。
// 如果不能初始化成功,在Block回調(diào)中打印錯誤,方便調(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類中橋敲以上代碼。也可以新建一個專門負(fù)責(zé)儲存功能的類,在這個類中敲這段代碼。(我一般不喜歡將這部分代碼放在AppDelegate類中,所以我創(chuàng)建工程的時候,都不會勾選Use Core Data)。

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

因為NSPersistentContainer不兼容iOS10之前的系統(tǒng)。所以,如果你已經(jīng)用了NSPersistentContainer初始化了Core Data Stack,但同時也要兼容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文件(我們會在初始化完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文件的屬性——因為其他類也要用到這個屬性))
    _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)然,如果你不想做這個判斷,只用上面方法初始化即可,這個方法在新舊系統(tǒng)都正常工作。

情況3:直接勾選Core Data

創(chuàng)建項目時,如果直接勾選Core Data復(fù)選框,項目模版會在AppDelegate類中直接幫你初始化好Core Data Stack,自動創(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文件聲明一個NSPersistentContainer類型的屬性(為了讓其他類可以調(diào)用)
@property (readonly, strong) NSPersistentContainer *persistentContainer;

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

@end

在AppDelegate.m文件

@implementation AppDelegate

……

#pragma mark - Core Data stack

@synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    @synchronized (self) {
        if (_persistentContainer == nil) {
            // 實例化NSPersistentContainer對象。
            // 注意:參數(shù)傳入的名稱,就是.xcdatamodeld文件名稱(兩者需要一直)(勾選Core Data后,會自動創(chuàng)建一個.xcdatamodeld文件)
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataTestUseCoreData"];
            
            // 加載persistent stores,實現(xiàn)最終的Core Data stack的創(chuàng)建
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                // 如果有錯誤,打印出來
                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)建了一個NSPersistentContainer實例,以及一個saveContext方法。(并且已經(jīng)幫我們創(chuàng)建了.xcdatamodeld模型文件)

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

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

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

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

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

創(chuàng)建xcdatamodeld文件

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

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

這部分用一張圖概括:


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

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

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

創(chuàng)建NSManagedObject子類

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

為什么要用NSManagedObject子類

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

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

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

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

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

在.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中,將某個屬性實現(xiàn)為@dynamic,表示編譯器在編譯時不會對這個屬性的存取方法(getter/setter)做檢查(由程序員自己提供存取方法)。在Core Data中,由Core Data實現(xiàn)。
@dynamic name;
@dynamic userID;

@end

然后就可以這樣:

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

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

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

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

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

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

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

3、增

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

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

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

該方法會返回一個NSManagedObject,或其子類的對象,然后就可以對該對象進行賦值操作了。

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

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

4、刪

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

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

5、查(Fetching Objects)

查詢功能,是被官方特別強調(diào)的一個功能,據(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;
}

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

另外,還可以通過NSPredicate(「謂語」,也有翻譯為「斷言」的)進行數(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進行由小到大(升序)的排序
    NSSortDescriptor *userIDSort = [NSSortDescriptor sortDescriptorWithKey:@"userID" ascending:YES];
    
    // 注意,這個參數(shù)是一個數(shù)組,所以排序可以有多個條件,比如先按身高從低到高排,滿足此條件后再按照名字首字母A~Z從前到后排。這時候,身高的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ù)的情況比較相似,直接對屬性進行修改。先查詢到你要的數(shù)據(jù)對象,再重新賦值即可。

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

關(guān)于批處理,可以參考《New in Core Data and iOS 8: Batch Updating》,這里不再展開( 其實我自己暫時也沒用過: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)存中,并不會固化到沙盒中。

版本「遷移」

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

步驟:

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


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

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

大家也可以自己驗證一下,不進行版本遷移,直接修改.xcdatamodeld文件,然后運行程序,會報什么錯。

以上是自動、輕量化的版本遷移,至于更復(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)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,084評論 2 375

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