適讀對象:
- 需要入門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)目一直用NSKeyedArchiver和NSKeyedUnarchiver(固化)進(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之前涉及NSManagedObjectContext、NSPersistentStoreCoordinator、NSManagedObjectModel、NSPersistentStore這些類
- 創(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ù)(對象) -
刪:利用NSManagedObjectContext的
deleteObject:
方法刪除數(shù)據(jù) -
查:利用NSManagedObjectContext的
executeFetchRequest:error:
方法,查詢數(shù)據(jù) -
保存:利用NSManagedObjectContext的
save:
方法保存數(shù)據(jù)。
OK,基本上就是這些東西了~
術(shù)語
CoreData學(xué)習(xí)曲線陡峭的原因之一,術(shù)語太多算一個(gè)。所以這里整理一下,如下:

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)系
這部分用一張圖概括:

坑:這里有個(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」中修改,如下:

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)用NSManagedObjectContext的deleteObject:
方法即可。當(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)用NSManagedObjectContext的 executeFetchRequest: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)行修改,不利于性能,所以可以使用NSBatchUpdateRequest 、NSBatchDeleteRequest,進(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)用NSManagedObjectContext的hasChanges
方法,來判斷:在數(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文件進(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 Model Versioning and Data Migration Programming Guide
End
《認(rèn)識CoreData-初識CoreData》系列文章,寫得很詳細(xì),推薦閱讀。
以上就是Core Data的入門用法(文科狗,不容易啊 XD )。