認(rèn)識(shí)CoreData - 使用進(jìn)階

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

配圖

之前兩篇文章都比較偏理論牍颈,文字表達(dá)比較多一些茵臭,但都是干貨迁杨!學(xué)習(xí)時(shí)先理解理論知識(shí),才能更好的幫助后面的理解唉匾。

在這篇文章中孕讳,將會(huì)涉及關(guān)于CoreData的一些復(fù)雜操作,這些操作會(huì)涉及分頁(yè)查詢肄鸽、模糊查詢卫病、批處理等高級(jí)操作。

通過(guò)這些操作可以更好的使用CoreData典徘,提升CoreData性能蟀苛。文章中將會(huì)出現(xiàn)大量示例代碼,通過(guò)代碼的方式更有助于理解逮诲。
文章內(nèi)容還會(huì)比較多帜平,希望各位耐心看完。

文章中如有疏漏或錯(cuò)誤梅鹦,還請(qǐng)各位及時(shí)提出裆甩,謝謝!??


NSPredicate

概述

iOS開發(fā)過(guò)程中齐唆,很多需求都需要用到過(guò)濾條件嗤栓。例如過(guò)濾一個(gè)集合對(duì)象中存儲(chǔ)的對(duì)象,可以通過(guò)Foundation框架下的NSPredicate類來(lái)執(zhí)行這個(gè)操作。

CoreData中可以通過(guò)設(shè)置NSFetchRequest類的predicate屬性茉帅,來(lái)設(shè)置一個(gè)NSPredicate類型的謂詞對(duì)象當(dāng)做過(guò)濾條件叨叙。通過(guò)設(shè)置這個(gè)過(guò)濾條件,可以只獲取符合過(guò)濾條件的托管對(duì)象堪澎,不會(huì)將所有托管對(duì)象都加載到內(nèi)存中擂错。這樣是非常節(jié)省內(nèi)存和加快查找速度的,設(shè)計(jì)一個(gè)好的NSPredicate可以優(yōu)化CoreData搜索性能樱蛤。

語(yǔ)法

NSPredicate更加偏向于自然語(yǔ)言钮呀,不像SQLite一樣有很多固定的語(yǔ)法,看起來(lái)也更加清晰易懂昨凡。例如下面需要查找條件為年齡30歲以上爽醋,并且包括30歲的條件。

[NSPredicate predicateWithFormat:@"age >= 30"]
過(guò)濾集合對(duì)象

可以通過(guò)NSPredicate對(duì)iOS中的集合對(duì)象執(zhí)行過(guò)濾操作土匀,可以是NSArray子房、NSSet及其子類。

對(duì)不可變數(shù)組NSArray執(zhí)行的過(guò)濾就轧,過(guò)濾后會(huì)返回一個(gè)NSArray類型的結(jié)果數(shù)組,其中存儲(chǔ)著符合過(guò)濾條件的對(duì)象田度。

NSArray *results = [array filteredArrayUsingPredicate:predicate]

對(duì)可變數(shù)組NSMutableArray執(zhí)行的過(guò)濾條件妒御,過(guò)濾后會(huì)直接改變?cè)蠈?duì)象內(nèi)部存儲(chǔ)的對(duì)象,刪除不符合條件的對(duì)象镇饺。

[arrayM filterUsingPredicate:predicate]
復(fù)合過(guò)濾條件

謂詞不只可以過(guò)濾簡(jiǎn)單條件乎莉,還可以過(guò)濾復(fù)雜條件,設(shè)置復(fù)合過(guò)濾條件奸笤。

[NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]

當(dāng)然也可以通過(guò)NSCompoundPredicate對(duì)象來(lái)設(shè)置復(fù)合過(guò)濾條件惋啃,返回結(jié)果是一個(gè)NSPredicate的子類NSCompoundPredicate對(duì)象。

[[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:@[predicate1, predicate2]]

枚舉值NSCompoundPredicateType參數(shù)监右,可以設(shè)置三種復(fù)合條件边灭,枚舉值非常直觀很容易看懂。

  • NSNotPredicateType
  • NSAndPredicateType
  • NSOrPredicateType
基礎(chǔ)語(yǔ)法

下面是列舉的一些NSPredicate的基礎(chǔ)語(yǔ)法健盒,這些語(yǔ)法看起來(lái)非常容易理解绒瘦,更復(fù)雜的用法可以去看蘋果的官方API

語(yǔ)法 作用
== 判斷是否相等
>= 大于或等于
<= 小于或等于
> 大于
< 小于
!= 不等于
AND 或 &&
OR 或 II
NOT 或 !
正則表達(dá)式

NSPredicate中還可以使用正則表達(dá)式扣癣,可以通過(guò)正則表達(dá)式完成一些復(fù)雜需求惰帽,這使得謂詞的功能更加強(qiáng)大,例如下面是一個(gè)手機(jī)號(hào)驗(yàn)證的正則表達(dá)式父虑。

NSString *mobile = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";
NSPredicate *regexmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", mobile];
模糊查詢

NSPredicate支持對(duì)數(shù)據(jù)的模糊查詢该酗,例如下面使用通配符來(lái)匹配包含lxz的結(jié)果,具體CoreData中的使用在下面會(huì)講到士嚎。

[NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"]
keyPath

NSPredicate在創(chuàng)建查詢條件時(shí)呜魄,還支持設(shè)置被匹配目標(biāo)的keyPath烁焙,也就是設(shè)置更深層被匹配的目標(biāo)。例如下面設(shè)置employeename屬性為查找條件耕赘,就是用點(diǎn)語(yǔ)法設(shè)置的keyPath骄蝇。

[NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"]

設(shè)置查詢條件

在之前的文章中,執(zhí)行下面MOCfetchRequest方法操骡,一般都需要傳入一個(gè)NSFetchRequest類型的參數(shù)九火。這個(gè)request參數(shù)可以做一些設(shè)置操作,這樣就可以以較優(yōu)的性能獲取指定的數(shù)據(jù)册招。

- (nullable NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error;

NSFetchRequest

在執(zhí)行fetch操作前岔激,可以給NSFetchRequest設(shè)置一些參數(shù),這些參數(shù)包括謂詞是掰、排序等條件虑鼎,下面是一些基礎(chǔ)的設(shè)置。

  • 設(shè)置查找哪個(gè)實(shí)體键痛,從數(shù)據(jù)庫(kù)的角度來(lái)看就是查找哪張表炫彩,通過(guò)fetchRequestWithEntityName:或初始化方法來(lái)指定表名。

  • 通過(guò)NSPredicate類型的屬性絮短,可以設(shè)置查找條件江兢,這個(gè)屬性在開發(fā)中用得最多。NSPredicate可以包括固定格式的條件以及正則表達(dá)式丁频。

  • 通過(guò)sortDescriptors屬性杉允,可以設(shè)置獲取結(jié)果數(shù)組的排序方式,這個(gè)屬性是一個(gè)數(shù)組類型席里,也就是可以設(shè)置多種排序條件叔磷。(但是注意條件不要沖突)

  • 通過(guò)fetchOffset屬性設(shè)置從查詢結(jié)果的第幾個(gè)開始獲取,通過(guò)fetchLimit屬性設(shè)置每次獲取多少個(gè)奖磁。主要用于分頁(yè)查詢改基,后面會(huì)講。

MOC執(zhí)行fetch操作后署穗,獲取的結(jié)果是以數(shù)組的形式存儲(chǔ)的寥裂,數(shù)組中存儲(chǔ)的就是托管對(duì)象。NSFetchRequest提供了參數(shù)resultType案疲,參數(shù)類型是一個(gè)枚舉類型封恰。通過(guò)這個(gè)參數(shù),可以設(shè)置執(zhí)行fetch操作后返回的數(shù)據(jù)類型褐啡。

  • NSManagedObjectResultType: 返回值是NSManagedObject的子類诺舔,也就是托管對(duì)象,這是默認(rèn)選項(xiàng)。

  • NSManagedObjectIDResultType: 返回NSManagedObjectID類型的對(duì)象低飒,也就是NSManagedObjectID许昨,對(duì)內(nèi)存占用比較小。MOC可以通過(guò)NSManagedObjectID對(duì)象獲取對(duì)應(yīng)的托管對(duì)象褥赊,并且可以通過(guò)緩存NSManagedObjectID參數(shù)來(lái)節(jié)省內(nèi)存消耗糕档。

  • NSDictionaryResultType: 返回字典類型對(duì)象。

  • NSCountResultType: 返回請(qǐng)求結(jié)果的count值拌喉,這個(gè)操作是發(fā)生在數(shù)據(jù)庫(kù)層級(jí)的速那,并不需要將數(shù)據(jù)加載到內(nèi)存中。

設(shè)置獲取條件

// 建立獲取數(shù)據(jù)的請(qǐng)求對(duì)象尿背,并指明操作Employee表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 設(shè)置請(qǐng)求條件端仰,通過(guò)設(shè)置的條件,來(lái)過(guò)濾出需要的數(shù)據(jù)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
request.predicate = predicate;

// 設(shè)置請(qǐng)求結(jié)果排序方式田藐,可以設(shè)置一個(gè)或一組排序方式荔烧,最后將所有的排序方式添加到排序數(shù)組中
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
// NSSortDescriptor的操作都是在SQLite層級(jí)完成的,不會(huì)將對(duì)象加載到內(nèi)存中汽久,所以對(duì)內(nèi)存的消耗是非常小的
request.sortDescriptors = @[sort];

// 執(zhí)行獲取請(qǐng)求操作鹤竭,獲取的托管對(duì)象將會(huì)被存儲(chǔ)在一個(gè)數(shù)組中并返回
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Employee Name : %@, Height : %@, Brithday : %@", obj.name, obj.height, obj.brithday);
}];

// 錯(cuò)誤處理
if (error) {
    NSLog(@"CoreData Fetch Data Error : %@", error);
}

這里設(shè)置NSFetchRequest對(duì)象的一些請(qǐng)求條件,設(shè)置查找Employee表中namelxz的數(shù)據(jù)回窘,并且將所有符合的數(shù)據(jù)用height升序的方式排列诺擅。

有實(shí)體關(guān)聯(lián)關(guān)系

一個(gè)模型文件中的不同實(shí)體間,可以設(shè)置實(shí)體間的關(guān)聯(lián)關(guān)系啡直,這個(gè)在之前的文章中講過(guò)。實(shí)體關(guān)聯(lián)關(guān)系分為對(duì)一對(duì)多苍碟,也可以設(shè)置是否雙向關(guān)聯(lián)酒觅。

這里演示的實(shí)體只是簡(jiǎn)單的To One的關(guān)系,并且下面會(huì)給出設(shè)置是否雙向關(guān)聯(lián)的區(qū)別對(duì)比微峰。

插入實(shí)體

// 創(chuàng)建托管對(duì)象舷丹,并將其關(guān)聯(lián)到指定的MOC上
Employee *zsEmployee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
zsEmployee.name = @"zhangsan";
zsEmployee.height = @1.9f;
zsEmployee.brithday = [NSDate date];

Employee *lsEmployee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
lsEmployee.name = @"lisi";
lsEmployee.height = @1.7f;
lsEmployee.brithday = [NSDate date];

Department *iosDepartment = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:context];
iosDepartment.departName = @"iOS";
iosDepartment.createDate = [NSDate date];
iosDepartment.employee = zsEmployee;

Department *androidDepartment = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:context];
androidDepartment.departName = @"android";
androidDepartment.createDate = [NSDate date];
androidDepartment.employee = lsEmployee;

// 執(zhí)行存儲(chǔ)操作
NSError *error = nil;
if (context.hasChanges) {
    [context save:&error];
}

// 錯(cuò)誤處理
if (error) {
    NSLog(@"Association Table Add Data Error : %@", error);
}

上面創(chuàng)建了四個(gè)實(shí)體,并且將Employee都關(guān)聯(lián)到Department上蜓肆,完成關(guān)聯(lián)操作后通過(guò)MOC存儲(chǔ)到本地颜凯。

可以看到上面所有的托管對(duì)象創(chuàng)建時(shí),都使用NSEntityDescriptioninsert方法創(chuàng)建仗扬,并和上下文建立關(guān)系症概。這時(shí)就想問(wèn)了,我能直接采用傳統(tǒng)的init方法創(chuàng)建嗎早芭?

會(huì)崩的??彼城!創(chuàng)建托管對(duì)象時(shí)需要指定MOC,在運(yùn)行時(shí)動(dòng)態(tài)的生成setget方法募壕。但是直接通過(guò)init方法初始化的對(duì)象调炬,系統(tǒng)是不知道這里是需要系統(tǒng)自身生成setget方法的舱馅,而且系統(tǒng)也不知道應(yīng)該對(duì)應(yīng)哪個(gè)MOC缰泡,會(huì)導(dǎo)致方法未實(shí)現(xiàn)的崩潰。所以就出現(xiàn)了開發(fā)中經(jīng)常出現(xiàn)的錯(cuò)誤代嗤,如下面崩潰信息:

-[Employee setName:]: unrecognized selector sent to instance 0x7fa665900f60

雙向關(guān)聯(lián)

在上一篇文章中提到過(guò)雙向關(guān)聯(lián)的概念棘钞,也就是設(shè)置Relationship時(shí)Inverse是否為空。下面是EmployeeDepartment在數(shù)據(jù)庫(kù)中资溃,設(shè)置inverse和沒(méi)有設(shè)置inverse的兩種數(shù)據(jù)存儲(chǔ)武翎,可以很清晰的對(duì)比出設(shè)置雙向關(guān)聯(lián)的區(qū)別。
測(cè)試代碼還是用上面插入實(shí)體的代碼溶锭,只是更改inverse選項(xiàng)宝恶。

設(shè)置雙向關(guān)聯(lián)
Employee
Department
未設(shè)置雙向關(guān)聯(lián)
Employee
Department

從圖中可以看出,未設(shè)置雙向關(guān)聯(lián)的實(shí)體趴捅,Department關(guān)聯(lián)Employee為屬性并存儲(chǔ)后垫毙,Department表中的關(guān)系是存在的,但Employee表中的關(guān)系依然是空的拱绑。而設(shè)置雙向關(guān)聯(lián)后的實(shí)體综芥,在Department關(guān)聯(lián)Employee為屬性并存儲(chǔ)后,Employee在表中自動(dòng)設(shè)置了和Department的關(guān)系猎拨。

雙向關(guān)聯(lián)的關(guān)系不只體現(xiàn)在數(shù)據(jù)庫(kù)中膀藐,在程序運(yùn)行過(guò)程中托管對(duì)象的關(guān)聯(lián)屬性,也是隨著發(fā)生變化的红省。雙向關(guān)聯(lián)的雙方额各,一方的關(guān)聯(lián)屬性設(shè)置關(guān)系后,另一方關(guān)聯(lián)屬性的關(guān)系也會(huì)發(fā)生變化吧恃。用下面的代碼打印一下各自的關(guān)聯(lián)屬性虾啦,結(jié)果和上面數(shù)據(jù)庫(kù)的變化是一樣的。

NSLog(@"Department : %@, Employee : %@", androidDepartment.employee, lsEmployee.department);

查詢操作

// 創(chuàng)建獲取數(shù)據(jù)的請(qǐng)求對(duì)象痕寓,并指明操作Department表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Department"];

// 設(shè)置請(qǐng)求條件傲醉,設(shè)置employee的name為請(qǐng)求條件。NSPredicate的好處在于呻率,可以設(shè)置keyPath條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"];
request.predicate = predicate;

// 執(zhí)行查找操作
NSError *error = nil;
NSArray<Department *> *departments = [context executeFetchRequest:request error:&error];
[departments enumerateObjectsUsingBlock:^(Department * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Department Search Result DepartName : %@, employee name : %@", obj.departName, obj.employee.name);
}];

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

查找Department實(shí)體硬毕,并打印實(shí)體內(nèi)容。就像上面講的雙向關(guān)系一樣筷凤,有關(guān)聯(lián)關(guān)系的實(shí)體昭殉,自己被查找出來(lái)后苞七,也會(huì)將與之關(guān)聯(lián)的其他實(shí)體也查找出來(lái),并且查找出來(lái)的實(shí)體都是關(guān)聯(lián)著MOC的挪丢。

分頁(yè)查詢

在從本地存儲(chǔ)區(qū)獲取數(shù)據(jù)時(shí)蹂风,可以指定從第幾個(gè)獲取,以及本次查詢獲取多少個(gè)數(shù)據(jù)乾蓬,聯(lián)合起來(lái)使用就是分頁(yè)查詢惠啄。當(dāng)然也可以根據(jù)需求,單獨(dú)使用這兩個(gè)API任内。

這種需求在實(shí)際開發(fā)中非常常見撵渡,例如TableView中,上拉加載數(shù)據(jù)死嗦,每次加載20條數(shù)據(jù)趋距,就可以利用分頁(yè)查詢輕松實(shí)現(xiàn)。

// 創(chuàng)建獲取數(shù)據(jù)的請(qǐng)求對(duì)象越除,并指明操作Employee表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 設(shè)置查找起始點(diǎn)节腐,這里是從搜索結(jié)果的第六個(gè)開始獲取
request.fetchOffset = 6;

// 設(shè)置分頁(yè),每次請(qǐng)求獲取六個(gè)托管對(duì)象
request.fetchLimit = 6;

// 設(shè)置排序規(guī)則摘盆,這里設(shè)置身高升序排序
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
request.sortDescriptors = @[descriptor];

// 執(zhí)行查詢操作
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Page Search Result Name : %@, height : %@", obj.name, obj.height);
}];

// 錯(cuò)誤處理
if (error) {
    NSLog(@"Page Search Data Error : %@", error);
}

上面是一個(gè)按照身高升序排序翼雀,分頁(yè)獲取搜索結(jié)果的例子。查找Employee表中的實(shí)體孩擂,將結(jié)果按照height字段升序排序狼渊,并從結(jié)果的第六個(gè)開始查找,并且設(shè)置獲取的數(shù)量也是六個(gè)类垦。

模糊查詢

有時(shí)需要獲取具有某些相同特征的數(shù)據(jù)狈邑,這樣就需要對(duì)查詢的結(jié)果做模糊匹配。在CoreData執(zhí)行模糊匹配時(shí)蚤认,可以通過(guò)NSPredicate執(zhí)行這個(gè)操作官地。

// 創(chuàng)建獲取數(shù)據(jù)的請(qǐng)求對(duì)象,設(shè)置對(duì)Employee表進(jìn)行操作
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 創(chuàng)建模糊查詢條件烙懦。這里設(shè)置的帶通配符的查詢,查詢條件是結(jié)果包含lxz
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
request.predicate = predicate;

// 執(zhí)行查詢操作
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Fuzzy Search Result Name : %@, height : %@", obj.name, obj.height);
}];

// 錯(cuò)誤處理
if (error) {
    NSLog(@"Fuzzy Search Data Error : %@", error);
}

上面是使用通配符的方式進(jìn)行模糊查詢赤炒,NSPredicate支持多種形式的模糊查詢氯析,下面列舉一些簡(jiǎn)單的匹配方式。模糊查詢條件對(duì)大小寫不敏感莺褒,所以查詢條件大小寫均可掩缓。

  • 以lxz開頭

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", @"lxz"];
    
  • 以lxz結(jié)尾

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name ENDSWITH %@", @"lxz"];
    
  • 其中包含lxz

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains %@", @"lxz"];
    
  • 查詢條件結(jié)果包含lxz

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
    

加載請(qǐng)求模板

在之前的文章中談到在模型文件中設(shè)置請(qǐng)求模板,也就是在.xcdatamodeld文件中遵岩,設(shè)置Fetch Requests你辣,使用時(shí)可以通過(guò)對(duì)應(yīng)的NSManagedObjectModel獲取設(shè)置好的模板巡通。

.... 省略上下文創(chuàng)建步驟 ....
// 通過(guò)MOC獲取模型文件對(duì)應(yīng)的托管對(duì)象模型
NSManagedObjectModel *model = context.persistentStoreCoordinator.managedObjectModel;
// 通過(guò).xcdatamodeld文件中設(shè)置的模板名,獲取請(qǐng)求對(duì)象
NSFetchRequest *fetchRequest = [model fetchRequestTemplateForName:@"EmployeeFR"];

// 請(qǐng)求數(shù)據(jù)舍哄,下面的操作和普通請(qǐng)求一樣
NSError *error = nil;
NSArray<Employee *> *dataList = [context executeFetchRequest:fetchRequest error:&error];
[dataList enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Employee.count = %ld, Employee.height = %f", dataList.count, [obj.height floatValue]);
}];

// 錯(cuò)誤處理
if (error) {
    NSLog(@"Execute Fetch Request Error : %@", error);
}

獲取結(jié)果Count值

開發(fā)過(guò)程中有時(shí)需要只獲取所需數(shù)據(jù)的Count值宴凉,也就是執(zhí)行獲取操作后數(shù)組中所存儲(chǔ)的對(duì)象數(shù)量。遇到這個(gè)需求表悬,如果像之前一樣MOC執(zhí)行獲取操作弥锄,獲取到數(shù)組然后取Count,這樣對(duì)內(nèi)存消耗是很大的蟆沫。

對(duì)于這個(gè)需求籽暇,蘋果提供了兩種常用的方式獲取這個(gè)Count值。這兩種獲取操作饭庞,都是在數(shù)據(jù)庫(kù)中完成的戒悠,并不需要將托管對(duì)象加載到內(nèi)存中,對(duì)內(nèi)存的開銷也是很小的舟山。

方法1绸狐,設(shè)置resultType

// 設(shè)置過(guò)濾條件,可以根據(jù)需求設(shè)置自己的過(guò)濾條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"];
// 創(chuàng)建請(qǐng)求對(duì)象捏顺,并指明操作Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
fetchRequest.predicate = predicate;
// 這一步是關(guān)鍵六孵。設(shè)置返回結(jié)果類型為Count,返回結(jié)果為NSNumber類型
fetchRequest.resultType = NSCountResultType;

// 執(zhí)行查詢操作幅骄,返回的結(jié)果還是數(shù)組劫窒,數(shù)組中只存在一個(gè)對(duì)象,就是計(jì)算出的Count值
NSError *error = nil;
NSArray *dataList = [context executeFetchRequest:fetchRequest error:&error];
NSInteger count = [dataList.firstObject integerValue];
NSLog(@"fetch request result Employee.count = %ld", count);

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

方法1中設(shè)置NSFetchRequest對(duì)象的resultTypeNSCountResultType拆座,獲取到結(jié)果的Count值主巍。這個(gè)枚舉值在之前的文章中提到過(guò),除了Count參數(shù)挪凑,還可以設(shè)置其他三種參數(shù)孕索。

方法2,使用MOC提供的方法

// 設(shè)置過(guò)濾條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"];
// 創(chuàng)建請(qǐng)求對(duì)象躏碳,指明操作Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
fetchRequest.predicate = predicate;

// 通過(guò)調(diào)用MOC的countForFetchRequest:error:方法搞旭,獲取請(qǐng)求結(jié)果count值,返回結(jié)果直接是NSUInteger類型變量
NSError *error = nil;
NSUInteger count = [context countForFetchRequest:fetchRequest error:&error];
NSLog(@"fetch request result count is : %ld", count);

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

MOC提供了專門獲取請(qǐng)求結(jié)果Count值的方法菇绵,通過(guò)這個(gè)方法可以直接返回一個(gè)NSUInteger類型的Count值肄渗,使用起來(lái)比上面的方法更方便點(diǎn),其他都是一樣的咬最。

位運(yùn)算

假設(shè)有需求是對(duì)Employee表中翎嫡,所有托管對(duì)象的height屬性計(jì)算總和。這個(gè)需求在數(shù)據(jù)量比較大的情況下永乌,將所有托管對(duì)象加載到內(nèi)存中是非常消耗內(nèi)存的惑申,就算批量加載也比較耗時(shí)耗內(nèi)存具伍。

CoreData對(duì)于這樣的需求,提供了位運(yùn)算的功能圈驼。MOC在執(zhí)行請(qǐng)求時(shí)人芽,是支持對(duì)數(shù)據(jù)進(jìn)行位運(yùn)算的。這個(gè)操作依然是在數(shù)據(jù)庫(kù)層完成的碗脊,對(duì)內(nèi)存的占用非常小啼肩。

// 創(chuàng)建請(qǐng)求對(duì)象,指明操作Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 設(shè)置返回值為字典類型衙伶,這是為了結(jié)果可以通過(guò)設(shè)置的name名取出祈坠,這一步是必須的
fetchRequest.resultType = NSDictionaryResultType;

// 創(chuàng)建描述對(duì)象
NSExpressionDescription *expressionDes = [[NSExpressionDescription alloc] init];
// 設(shè)置描述對(duì)象的name,最后結(jié)果需要用這個(gè)name當(dāng)做key來(lái)取出結(jié)果
expressionDes.name = @"sumOperatin";
// 設(shè)置返回值類型矢劲,根據(jù)運(yùn)算結(jié)果設(shè)置類型
expressionDes.expressionResultType = NSFloatAttributeType;

// 創(chuàng)建具體描述對(duì)象赦拘,用來(lái)描述對(duì)那個(gè)屬性進(jìn)行什么運(yùn)算(可執(zhí)行的運(yùn)算類型很多,這里描述的是對(duì)height屬性芬沉,做sum運(yùn)算)
NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"height"]]];
// 只能對(duì)應(yīng)一個(gè)具體描述對(duì)象
expressionDes.expression = expression;
// 給請(qǐng)求對(duì)象設(shè)置描述對(duì)象躺同,這里是一個(gè)數(shù)組類型,也就是可以設(shè)置多個(gè)描述對(duì)象
fetchRequest.propertiesToFetch = @[expressionDes];

// 執(zhí)行請(qǐng)求丸逸,返回值還是一個(gè)數(shù)組蹋艺,數(shù)組中只有一個(gè)元素,就是存儲(chǔ)計(jì)算結(jié)果的字典
NSError *error = nil;
NSArray *resultArr = [context executeFetchRequest:fetchRequest error:&error];
// 通過(guò)上面設(shè)置的name值黄刚,當(dāng)做請(qǐng)求結(jié)果的key取出計(jì)算結(jié)果
NSNumber *number = resultArr.firstObject[@"sumOperatin"];
NSLog(@"fetch request result is %f", [number floatValue]);

// 錯(cuò)誤處理
if (error) {
    NSLog(@"fetch request result error : %@", error);
}
執(zhí)行結(jié)果
執(zhí)行結(jié)果

從執(zhí)行結(jié)果可以看到捎谨,MOC對(duì)所有查找到的托管對(duì)象height屬性執(zhí)行了求和操作,并將結(jié)果放在字典中返回憔维。位運(yùn)算主要是通過(guò)NSFetchRequest對(duì)象的propertiesToFetch屬性設(shè)置涛救,這個(gè)屬性可以設(shè)置多個(gè)描述對(duì)象,最后通過(guò)不同的name當(dāng)做key來(lái)取出結(jié)果即可业扒。

NSExpression類可以描述多種運(yùn)算检吆,可以在NSExpression.h文件中的注釋部分,看到所有支持的運(yùn)算類型程储,大概看了一下有二十多種運(yùn)算蹭沛。而且除了上面NSExpression調(diào)用的方法,此類還支持點(diǎn)語(yǔ)法的位運(yùn)算章鲤,例如下面的例子致板。

[NSExpression expressionWithFormat:@"@sum.height"];

批處理

在使用CoreData之前,我和公司同事也討論過(guò)咏窿,假設(shè)遇到需要大量數(shù)據(jù)處理的時(shí)候怎么辦。CoreData對(duì)于大量數(shù)據(jù)處理的靈活性肯定不如SQLite素征,這時(shí)候還需要自己使用其他方式優(yōu)化數(shù)據(jù)處理集嵌。雖然在移動(dòng)端這種情況很少出現(xiàn)萝挤,但是在持久層設(shè)計(jì)時(shí)還是要考慮這方面。

當(dāng)需要進(jìn)行數(shù)據(jù)的處理時(shí)根欧,CoreData需要先將數(shù)據(jù)加載到內(nèi)存中怜珍,然后才能對(duì)數(shù)據(jù)進(jìn)行處理。這樣對(duì)于大量數(shù)據(jù)來(lái)說(shuō)凤粗,都加載到內(nèi)存中是非常消耗內(nèi)存的酥泛,而且容易導(dǎo)致崩潰的發(fā)生。如果遇到更改所有數(shù)據(jù)的某個(gè)字段這樣的簡(jiǎn)單需求嫌拣,需要將相關(guān)的托管對(duì)象都加載到內(nèi)存中柔袁,然后進(jìn)行更改、保存异逐。

對(duì)于上面這樣的問(wèn)題捶索,CoreDataiOS8推出了批量更新API,通過(guò)這個(gè)API可以直接在數(shù)據(jù)庫(kù)一層就完成更新操作灰瞻,而不需要將數(shù)據(jù)加載到內(nèi)存腥例。除了批量更新操作,在iOS9中還推出了批量刪除API酝润,也是在數(shù)據(jù)庫(kù)一層完成的操作燎竖。關(guān)于批處理的API很多都是iOS8iOS9出來(lái)的要销,使用時(shí)需要注意版本兼容构回。

但是有個(gè)問(wèn)題,批量更新和批量刪除的兩個(gè)API蕉陋,都是直接對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作捐凭,更新完之后會(huì)導(dǎo)致MOC緩存和本地持久化數(shù)據(jù)不同步的問(wèn)題。所以需要手動(dòng)刷新受影響的MOC中存儲(chǔ)的托管對(duì)象凳鬓,使MOC和本地統(tǒng)一茁肠。假設(shè)你使用了NSFetchedResultsController,為了保證界面和數(shù)據(jù)的統(tǒng)一缩举,這一步更新操作更需要做垦梆。

批量更新

// 創(chuàng)建批量更新對(duì)象,并指明操作Employee表仅孩。
NSBatchUpdateRequest *updateRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Employee"];
// 設(shè)置返回值類型托猩,默認(rèn)是什么都不返回(NSStatusOnlyResultType),這里設(shè)置返回發(fā)生改變的對(duì)象Count值
updateRequest.resultType = NSUpdatedObjectsCountResultType;
// 設(shè)置發(fā)生改變字段的字典
updateRequest.propertiesToUpdate = @{@"height" : [NSNumber numberWithFloat:5.f]};

// 執(zhí)行請(qǐng)求后辽慕,返回值是一個(gè)特定的result對(duì)象京腥,通過(guò)result的屬性獲取返回的結(jié)果。MOC的這個(gè)API是從iOS8出來(lái)的溅蛉,所以需要注意版本兼容公浪。
NSError *error = nil;
NSBatchUpdateResult *result = [context executeRequest:updateRequest error:&error];
NSLog(@"batch update count is %ld", [result.result integerValue]);

// 錯(cuò)誤處理
if (error) {
    NSLog(@"batch update request result error : %@", error);
}

// 更新MOC中的托管對(duì)象他宛,使MOC和本地持久化區(qū)數(shù)據(jù)同步
[context refreshAllObjects];

上面對(duì)Employee表中所有的托管對(duì)象height值做了批量更新,在更新時(shí)通過(guò)設(shè)置propertiesToUpdate字典來(lái)控制更新字段和更新的值欠气,設(shè)置格式是字段名 : 新值厅各。通過(guò)設(shè)置批處理對(duì)象的predicate屬性,設(shè)置一個(gè)謂詞對(duì)象來(lái)控制受影響的對(duì)象预柒。

還可以對(duì)多個(gè)存儲(chǔ)區(qū)(數(shù)據(jù)庫(kù))做同樣批處理操作队塘,通過(guò)設(shè)置其父類affectedStores屬性,類型是一個(gè)數(shù)組宜鸯,可以包含受影響的存儲(chǔ)區(qū)憔古,多個(gè)存儲(chǔ)區(qū)的操作對(duì)批量刪除同樣適用

MOC在執(zhí)行請(qǐng)求方法時(shí)顾翼,發(fā)現(xiàn)方法名也不一樣了投放,執(zhí)行的是executeRequest: error:方法,這個(gè)方法是從iOS8之后出來(lái)的适贸。方法傳入的參數(shù)是NSBatchUpdateRequest類灸芳,此類并不是繼承自NSFetchRequest類,而是直接繼承自NSPersistentStoreRequest拜姿,和NSFetchRequest是平級(jí)關(guān)系烙样。

批量刪除

// 創(chuàng)建請(qǐng)求對(duì)象,并指明對(duì)Employee表做操作
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 通過(guò)謂詞設(shè)置過(guò)濾條件蕊肥,設(shè)置條件為height小于1.7
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < %f", 1.7f];
fetchRequest.predicate = predicate;

// 創(chuàng)建批量刪除請(qǐng)求谒获,并使用上面創(chuàng)建的請(qǐng)求對(duì)象當(dāng)做參數(shù)進(jìn)行初始化
NSBatchDeleteRequest *deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest];
// 設(shè)置請(qǐng)求結(jié)果類型,設(shè)置為受影響對(duì)象的Count
deleteRequest.resultType = NSBatchDeleteResultTypeCount;

// 使用NSBatchDeleteResult對(duì)象來(lái)接受返回結(jié)果壁却,通過(guò)id類型的屬性result獲取結(jié)果
NSError *error = nil;
NSBatchDeleteResult *result = [context executeRequest:deleteRequest error:&error];
NSLog(@"batch delete request result count is %ld", [result.result integerValue]);

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

// 更新MOC中的托管對(duì)象批狱,使MOC和本地持久化區(qū)數(shù)據(jù)同步
[context refreshAllObjects];

大多數(shù)情況下,涉及到托管對(duì)象的操作展东,都需要將其加載到內(nèi)存中完成赔硫。所以使用CoreData時(shí),需要注意內(nèi)存的使用,不要在內(nèi)存中存在過(guò)多的托管對(duì)象。在已經(jīng)做系統(tǒng)兼容的情況下断序,進(jìn)行大量數(shù)據(jù)的操作時(shí),應(yīng)該盡量使用批處理來(lái)完成操作诅需。

需要注意的是,refreshAllObjects是從iOS9出來(lái)的,在iOS9之前因?yàn)橐霭姹炯嫒荩孕枰褂?code>refreshObject: mergeChanges:方法更新托管對(duì)象耘成。

異步請(qǐng)求

// 創(chuàng)建請(qǐng)求對(duì)象,并指明操作Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 創(chuàng)建異步請(qǐng)求對(duì)象,并通過(guò)一個(gè)block進(jìn)行回調(diào)凿跳,返回結(jié)果是一個(gè)NSAsynchronousFetchResult類型參數(shù)
NSAsynchronousFetchRequest *asycFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult * _Nonnull result) {
    
    [result.finalResult enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"fetch request result Employee.count = %ld, Employee.name = %@", result.finalResult.count, obj.name);
    }];
}];

// 執(zhí)行異步請(qǐng)求件豌,和批量處理執(zhí)行同一個(gè)請(qǐng)求方法
NSError *error = nil;
[context executeRequest:asycFetchRequest error:&error];

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

上面通過(guò)NSAsynchronousFetchRequest對(duì)象創(chuàng)建了一個(gè)異步請(qǐng)求,并通過(guò)block進(jìn)行回調(diào)控嗜。如果有多個(gè)請(qǐng)求同時(shí)發(fā)起不需要擔(dān)心線程安全的問(wèn)題骡显,系統(tǒng)會(huì)將所有的異步請(qǐng)求添加到一個(gè)操作隊(duì)列中疆栏,在前一個(gè)任務(wù)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),CoreData會(huì)將數(shù)據(jù)庫(kù)加鎖惫谤,等前面的執(zhí)行完成才會(huì)繼續(xù)執(zhí)行后面的操作壁顶。

NSAsynchronousFetchRequest提供了cancel方法,也就是可以在請(qǐng)求過(guò)程中溜歪,將這個(gè)請(qǐng)求取消若专。還可以通過(guò)一個(gè)NSProgress類型的屬性,獲取請(qǐng)求完成進(jìn)度蝴猪。NSAsynchronousFetchRequest類從iOS8開始可以使用调衰,所以低版本需要做版本兼容。

需要注意的是自阱,執(zhí)行請(qǐng)求時(shí)MOC并發(fā)類型不能是NSConfinementConcurrencyType嚎莉,這個(gè)并發(fā)類型已經(jīng)被拋棄,會(huì)導(dǎo)致崩潰沛豌。


好多同學(xué)都問(wèn)我有Demo沒(méi)有趋箩,其實(shí)文章中貼出的代碼組合起來(lái)就是個(gè)Demo。后來(lái)想了想加派,還是給本系列文章配了一個(gè)簡(jiǎn)單的Demo叫确,方便大家運(yùn)行調(diào)試,后續(xù)會(huì)給所有博客的文章都加上Demo芍锦。

Demo只是來(lái)輔助讀者更好的理解文章中的內(nèi)容竹勉,應(yīng)該博客結(jié)合Demo一起學(xué)習(xí),只看Demo還是不能理解更深層的原理醉旦。Demo中幾乎每一行代碼都會(huì)有注釋饶米,各位可以打斷點(diǎn)跟著Demo執(zhí)行流程走一遍,看看各個(gè)階段變量的值车胡。

Demo地址劉小壯的Github


這兩天更新了一下文章檬输,將CoreData系列的六篇文章整合在一起,做了一個(gè)PDF版的《CoreData Book》匈棘,放在我Github上了丧慈。PDF上有文章目錄,方便閱讀。

如果你覺得不錯(cuò)逃默,請(qǐng)把PDF幫忙轉(zhuǎn)到其他群里鹃愤,或者你的朋友,讓更多的人了解CoreData完域,衷心感謝软吐!??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吟税,隨后出現(xiàn)的幾起案子凹耙,更是在濱河造成了極大的恐慌,老刑警劉巖肠仪,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肖抱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡异旧,警方通過(guò)查閱死者的電腦和手機(jī)意述,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吮蛹,“玉大人荤崇,你說(shuō)我怎么就攤上這事∑ヤ蹋” “怎么了天试?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)然低。 經(jīng)常有香客問(wèn)我喜每,道長(zhǎng),這世上最難降的妖魔是什么雳攘? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任带兜,我火速辦了婚禮,結(jié)果婚禮上吨灭,老公的妹妹穿的比我還像新娘刚照。我一直安慰自己,他們只是感情好喧兄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布无畔。 她就那樣靜靜地躺著,像睡著了一般吠冤。 火紅的嫁衣襯著肌膚如雪浑彰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天拯辙,我揣著相機(jī)與錄音郭变,去河邊找鬼颜价。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诉濒,可吹牛的內(nèi)容都是我干的周伦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼未荒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼专挪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起片排,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狈蚤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后划纽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锌畸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年勇劣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片潭枣。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡比默,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盆犁,到底是詐尸還是另有隱情命咐,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布谐岁,位于F島的核電站醋奠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏伊佃。R本人自食惡果不足惜窜司,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望航揉。 院中可真熱鬧塞祈,春花似錦、人聲如沸帅涂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)媳友。三九已至斯议,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庆锦,已是汗流浹背捅位。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人艇搀。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓尿扯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親焰雕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衷笋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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