【一】前言
Core Data框架提供了對象-關(guān)系映射(ORM)的功能锦聊,即能夠?qū)C對象轉(zhuǎn)化成數(shù)據(jù),保存在SQLite3數(shù)據(jù)庫文件中,也能夠?qū)⒈4嬖跀?shù)據(jù)庫中的數(shù)據(jù)還原成OC對象丢烘。在此數(shù)據(jù)操作期間俱箱,不需要編寫任何SQL語句国瓮。使用此功能,要添加CoreData.framework和導(dǎo)入主頭文件 <CoreData/CoreData.h>狞谱。
【二】各種類作用的介紹
創(chuàng)建Core Data Stack
- iOS10中利用NSPersistentContainer
- iOS10之前涉及NSManagedObjectContext揍拆、NSPersistentStoreCoordinator、NSManagedObjectModel哲嘲、NSPersistentStore這些類
【三】手動創(chuàng)建CoreData數(shù)據(jù)
我們創(chuàng)建一個和平常一樣的工程说墨,不需要勾選Use Core Data
:
一、創(chuàng)建模型文件
1与斤、進入創(chuàng)建新文件肪康,command+N
或者如下圖
2、選擇文件類型撩穿, 如下圖:
3磷支、設(shè)置文件名,如下圖:
4食寡、模型文件創(chuàng)建成功雾狈,會出現(xiàn)以后
建好后你會發(fā)現(xiàn)工程中多了 XXXXXXX.xcdatamodeld,我們需要在這里添加實體(首字母大寫)和實體的屬性抵皱。
二善榛、創(chuàng)建實體
1、利用可視化的方式創(chuàng)建實體呻畸,實體的功能就類似于我們的Model類移盆,具體操作如下如:
在傳統(tǒng)的項目中我們都使用OC變成,但是CoreData默認使用的是Swift語言伤为,所以我們要設(shè)置回來OC咒循,詳情見圖片
同時需要將codegen選為Manaul/None
這里我們需要創(chuàng)建Person和Card的實體以及實體屬性:
實體間的關(guān)系:選中Person實體,在Person中添加card屬性:
選中Card實體绞愚,在Card中添加person屬性:
添加完成后叙甸,他們關(guān)系如下:
三、創(chuàng)建實體類
利用可視化創(chuàng)建了實體位衩,但是我們要想獲取對應(yīng)的數(shù)據(jù)和名稱裆蒸,就必須關(guān)聯(lián)類,因此要創(chuàng)建實體類糖驴,創(chuàng)建步驟如下:
1光戈、選中 .xcdatamodeld 文件通過 Editor 創(chuàng)建:NSManagedObject subclass
類文件
2哪痰、生成了4個分類
分別為A+CoreDataClass.h, A+CoreDataClass.m, A+CoreDataProperties.h,A+CoreDataProperties.m
前2個為正式類文件(可以在需要用的地方直接引用這個類久妆,這個類內(nèi)部已經(jīng)引用了后面兩個類)晌杰, 后兩個為屬性類文件。
四筷弦、手動創(chuàng)建CoreData的使用
值得注意的是:下面的例子中我們可以直接使用創(chuàng)建的目的實體類如:Dog肋演,也可以使用NSManagedObject 這一公共實體類,可以使用KVC賦值烂琴,也可以使用 . 屬性 的方式直接賦值爹殊。
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_context];
[person setValue:@"lifengfeng" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:23] forKey:@"age"];
+++++++++++++++++ 另一個方式 +++++++++++++++++
Dog *dog = [NSEntityDescription insertNewObjectForEntityForName:@"Dog" inManagedObjectContext:self.myContext];
dog.name = @"name1";
dog.age = @"12";
for (Dog *obj in objs) {
NSLog(@"name=%@", obj.name);
}
1、搭建上下文環(huán)境
//1奸绷、創(chuàng)建模型對象
//獲取模型路徑
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"NewCodeDataModel" withExtension:@"momd"];//NewCodeDataModel.xcdatamodeld
//根據(jù)模型文件創(chuàng)建模型對象
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//2梗夸、創(chuàng)建持久化助理
//利用模型對象創(chuàng)建助理對象
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
//數(shù)據(jù)庫的名稱和路徑
NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *sqlPath = [docStr stringByAppendingPathComponent:@"mySqlite.sqlite"];
NSLog(@"path = %@", sqlPath);
NSURL *sqlUrl = [NSURL fileURLWithPath:sqlPath];
//設(shè)置數(shù)據(jù)庫相關(guān)信息
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlUrl options:nil error:nil];
//3、創(chuàng)建上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
//關(guān)聯(lián)持久化助理
[context setPersistentStoreCoordinator:store];
self.myContext = context;
其中
持久化存儲庫的類型(addPersistentStoreWithType:參數(shù)):
(1)NSSQLiteStoreType SQLite數(shù)據(jù)庫
(2)NSBinaryStoreType 二進制平面文件
(3)NSInMemoryStoreType 內(nèi)存庫号醉,無法永久保存數(shù)據(jù)
ConcurrencyType可選項(initWithConcurrencyType:參數(shù)):
(1)NSConfinementConcurrencyType 這個是默認項反症,每個線程一個獨立的Context,主要是為了兼容之前的設(shè)計畔派。
(2)NSPrivateQueueConcurrencyType 創(chuàng)建一個private queue(使用GCD)铅碍,這樣就不會阻塞主線程。
(3)NSMainQueueConcurrencyType 創(chuàng)建一個main queue线椰,使用主線程胞谈,會阻塞。
2憨愉、增:增加數(shù)據(jù)
/**
增加數(shù)據(jù)
*/
-(void)addData{
//傳入上下文烦绳,創(chuàng)建一個Person實體對象:
NSManagedObject *person =
[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_context];
//設(shè)置簡單屬性:
[person setValue:@"lifengfeng" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:23] forKey:@"age"];
//傳入上下文,創(chuàng)建一個Card實體對象:
NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:_context];
[card setValue:@"1234567890" forKey:@"no"];
//設(shè)置Person和Card之間的關(guān)聯(lián)關(guān)系:
[person setValue:card forKey:@"card"];
//利用上下文對象配紫,將數(shù)據(jù)同步到持久化存儲庫:
NSError *error = nil;
BOOL success = [_context save:&error];
if (!success) {
[NSException raise:@"訪問數(shù)據(jù)庫錯誤径密!" format:@"%@", [error localizedDescription]];
}else{
NSLog(@"訪問數(shù)據(jù)庫成功!");
}
// 如果是想做更新操作:只要在更改了實體對象的屬性后調(diào)用[context save:&error]笨蚁,就能將更改的數(shù)據(jù)同步到數(shù)據(jù)庫
}
3、刪:刪除數(shù)據(jù)
/**
刪除數(shù)據(jù)
*/
-(void)deleteData{
//建立請求趟庄,連接實體
NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
NSEntityDescription *person = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
request.entity = person;
//設(shè)置條件過濾(搜索name屬性中包含”lifengfeng“的那條記錄括细,注意等號必須加,可以有空格戚啥,也可以是==)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", @"lifengfeng"];
request.predicate = predicate;
//遍歷所有實體奋单,將每個實體的信息存放在數(shù)組中
NSArray *arr = [_context executeFetchRequest:request error:nil];
//刪除并保存
if(arr.count)
{
for (NSEntityDescription *p in arr)
{
[_context deleteObject:p];
NSLog(@"刪除%@成功!",p.name);
}
//保存
[_context save:nil];
}
}
4猫十、改:修改數(shù)據(jù)
/**
修改數(shù)據(jù)
*/
-(void)updateData{
//建立請求览濒,連接實體
NSFetchRequest *request = [[NSFetchRequest alloc] init] ;
NSEntityDescription *person = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
request.entity = person;
//設(shè)置條件過濾(搜索所有name屬性不為“l(fā)ifengfeng”的數(shù)據(jù))
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name!=%@", @"lifengfeng"];
request.predicate = predicate;
//遍歷所有實體呆盖,將每個實體的信息存放在數(shù)組中
NSArray *arr = [_context executeFetchRequest:request error:nil];
//更改并保存
if(arr.count)
{
for (NSEntityDescription *p in arr)
{
p.name = @"更改";
}
//保存
[_context save:nil];
}
else
{
NSLog(@"無檢索");
}
}
5、查:查詢數(shù)據(jù)
/**
查詢數(shù)據(jù)
*/
-(void)queryData{
//初始化一個查詢請求:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//設(shè)置要查詢的實體:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:_context];
request.entity = entity;
//設(shè)置排序(按照age降序):
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
//設(shè)置條件過濾(name like '%lifengfeng%'):
//設(shè)置條件過濾時贷笛,數(shù)據(jù)庫里面的%要用*來代替
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*lifengfeng*"];
request.predicate = predicate;
//執(zhí)行請求:
NSError *error = nil;
NSArray *objs = [_context executeFetchRequest:request error:&error];
if (error) {
[NSException raise:@"查詢錯誤" format:@"%@", [error localizedDescription]];
}
//遍歷數(shù)據(jù):
for (NSManagedObject *obj in objs) {
NSLog(@"name=%@", [obj valueForKey:@"name"]);
}
}
【四】使用系統(tǒng)自動創(chuàng)建的CoreData
系統(tǒng)幫我們在AppDelegate中創(chuàng)建了一個NSPersistentContainer實例应又,以及一個saveContext方法。(并且已經(jīng)幫我們創(chuàng)建了.xcdatamodeld模型文件)
注意看saveContext乏苦,我們通過NSPersistentContainer的屬性viewContext拿到NSManagedObjectContext對象株扛,再通過save:方法進行數(shù)據(jù)的保存。
因為系統(tǒng)并沒有幫我們適配舊系統(tǒng)汇荐,所以如果App要在非iOS10的舊系統(tǒng)運行洞就,還需要做類似上面 “搭建上下文環(huán)境”的工作,因為那里的代碼在iOS10以下和以上的代碼中都可以執(zhí)行掀淘。
如果是Xcode8之前的版本自動創(chuàng)建的Core Data Stack旬蟋,會不一樣(跟情況2類似),如下圖:
一個大坑:
這里有個坑革娄,在Xcode8中倾贰,Codegen下拉選擇框中增加了Class/Definition這一選項,而且是默認的預(yù)設(shè)值稠腊,這時候系統(tǒng)會自動幫我們這個實體創(chuàng)建了NSManagedObject子類躁染,我們不需要再創(chuàng)建實體類,最坑的是架忌,這些自動創(chuàng)建的類吞彤,在導(dǎo)航面板是看不見的!L痉拧饰恕!然后你很容易再重復(fù)手動創(chuàng)建NSManagedObject子類,這時候就會報類似「duplicate symbol _OBJC_METACLASS_Photography in:...」這類錯誤井仰。
所以埋嵌,如果你想自己手動創(chuàng)建NSManagedObject子類,就要把系統(tǒng)預(yù)設(shè)的Class/Definition改為Manual/None俱恶。
使用系統(tǒng)自動創(chuàng)建的CoreData時雹嗦,非常的方便,我們只需要在 xxxxx.xcdatamodeld 中添加好實體即可合是,然后就可以直接使用了了罪。
#import "Man+CoreDataClass.h"
#import "AppDelegate.h"
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSPersistentContainer *container = delegate.persistentContainer;
Man *man = [NSEntityDescription insertNewObjectForEntityForName:@"Man" inManagedObjectContext:container.viewContext];
man.name = @"小明";
man.height = @"180";
// ++++++++ 保存數(shù)據(jù) ++++++++
NSError *error = nil;
BOOL success = [container.viewContext save:&error];
if (!success) {
[NSException raise:@"訪問數(shù)據(jù)庫錯誤!" format:@"%@", [error localizedDescription]];
}else{
NSLog(@"訪問數(shù)據(jù)庫成功聪全!");
}
// ++++++++ 查詢數(shù)據(jù) ++++++++
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//設(shè)置要查詢的實體:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Man" inManagedObjectContext:container.viewContext];
request.entity = entity;
NSError *error1 = nil;
NSArray *objs = [container.viewContext executeFetchRequest:request error:&error];
if (error1) {
[NSException raise:@"查詢錯誤" format:@"%@", [error1 localizedDescription]];
}
//遍歷數(shù)據(jù):
for (NSManagedObject *obj in objs) {
NSLog(@"name=%@", [obj valueForKey:@"name"]);
}
【五】關(guān)于CoreData的版本遷移
應(yīng)用場景:修改了實體的數(shù)據(jù)結(jié)構(gòu)(比如說某個實體增加了一個特性)泊藕,因為APP版本更新后沙盒中的NSDocumentDirectory 中的緩存數(shù)據(jù)都不會被清除,這時候就要進行版本遷移了难礼,否則已經(jīng)安裝舊App的手機娃圆,在更新應(yīng)用后玫锋,兩邊數(shù)據(jù)結(jié)構(gòu)不一致導(dǎo)致不能識別,會崩潰讼呢。
步驟:
- 選中.xcdatamodeld文件撩鹿,Editor > Add Model Version,創(chuàng)建一個新版的.xcdatamodeld文件
- 切換到新版的.xcdatamodeld文件(切換成功后會有綠色的勾)吝岭,如下圖:
- 對.xcdatamodeld文件進行你想要的修改
- 創(chuàng)建NSPersistentStore的時候三痰,options參數(shù)傳一個dictionary,值如下:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
//在初始化的時候用到了版本遷移的設(shè)置
- (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的延遲加載
Core Data不會根據(jù)實體中的關(guān)聯(lián)關(guān)系立即獲取相應(yīng)的關(guān)聯(lián)對象,比如通過Core Data取出Person實體時失乾,并不會立即查詢相關(guān)聯(lián)的Card實體常熙;當應(yīng)用真的需要使用Card時,才會查詢數(shù)據(jù)庫碱茁,加載Card實體的信息裸卫。
【六】CoreData第三方庫:MagicalRecord
CoreData是蘋果自家推出的一個持久化框架,使用起來更加面向?qū)ο笈ⅰ5窃谑褂眠^程中會出現(xiàn)大量代碼墓贿,
而且CoreData學(xué)習(xí)曲線比較陡峭,如果掌握不好蜓氨,在使用過程中很容易造成其他問題聋袋。
國外開發(fā)者開源了一個基于CoreData封裝的第三方——MagicalRecord,就像是FMDB封裝SQLite一樣穴吹,
MagicalRecord封裝的CoreData幽勒,使得原生的CoreData更加容易使用。并且MagicalRecord降低了CoreData的使用門檻港令,
不用去手動管理之前的PSC啥容、MOC等對象。
添加MagicalRecord到項目中
將MagicalRecord
添加到項目中顷霹,和使用其他第三方一樣咪惠,可以通過下載源碼和CocoaPods
兩種方式添加。
但是不推薦直接拖源碼到項目中泼返,一是需要自己管理代碼更新硝逢,另一個原因是姨拥,直接拖源碼進項目是會報錯的绅喉,修改起來很麻煩渠鸽。
推薦通過CocoaPods
安裝MagicalRecord
,需要在Podfile
中加入下面命令柴罐,后續(xù)只需要通過命令來更新徽缚。
pod "MagicalRecord"
很多操作在這份MagicalRecord中文文檔中都說明的很清楚,這里作簡單歸納總結(jié)
1革屠、AppDelegate中的設(shè)置
#import <MagicalRecord/MagicalRecord.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[MagicalRecord setupCoreDataStack];
// ...
return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application
{
[MagicalRecord cleanUp];
}
2凿试、對象的儲存和查詢
#import <MagicalRecord/MagicalRecord.h>
// 獲取上下文環(huán)境
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
// 在當前上下文環(huán)境中創(chuàng)建一個新的 Person 對象.
Man *person = [Man MR_createEntityInContext:localContext];
person.name = @"MagicalRecord存儲";
// 保存修改到當前上下文中.
[localContext MR_saveToPersistentStoreAndWait];
NSArray *peopleArray = [Man MR_findAll];
for (Man *man in peopleArray) {
NSLog(@"名稱:%@",man.name);
}
對于MagicalRecord 的使用感受就是,確實如它的名稱一樣似芝,如此簡潔和方便的實現(xiàn)了對象的增刪改查那婉,如此的充滿魔力,關(guān)于的它的更多使用可以參考上面的中文文檔党瓮,相信大家都可以熟練掌握這個好用的類庫详炬!
另外還有其他的第三方存儲庫: 可以存對象的數(shù)據(jù)庫realm-cocoa使用時參考這篇文章:移動端數(shù)據(jù)庫新王者:realm
參考文章:
iOS 開發(fā)之 CoreData
CoreData的使用
iOS CoreData數(shù)據(jù)庫之創(chuàng)建詳解
「死磕」Core Data——入門
認識CoreData - 初識CoreData