該文章屬于劉小壯原創(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è)置employee
的name
屬性為查找條件耕赘,就是用點(diǎn)語(yǔ)法設(shè)置的keyPath
骄蝇。
[NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"]
設(shè)置查詢條件
在之前的文章中,執(zhí)行下面MOC
的fetchRequest
方法操骡,一般都需要傳入一個(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ì)象低飒,也就是NSManagedObject
的ID
许昨,對(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
表中name
為lxz
的數(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í),都使用NSEntityDescription
的insert
方法創(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)的生成set
、get
方法募壕。但是直接通過(guò)init
方法初始化的對(duì)象调炬,系統(tǒng)是不知道這里是需要系統(tǒng)自身生成set
、get
方法的舱馅,而且系統(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
是否為空。下面是Employee
和Department
在數(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)
未設(shè)置雙向關(guān)聯(lián)
從圖中可以看出,未設(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ì)象的resultType
為NSCountResultType
拆座,獲取到結(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é)果可以看到捎谨,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)題捶索,CoreData
在iOS8
推出了批量更新API,通過(guò)這個(gè)API
可以直接在數(shù)據(jù)庫(kù)一層就完成更新操作灰瞻,而不需要將數(shù)據(jù)加載到內(nèi)存腥例。除了批量更新操作,在iOS9
中還推出了批量刪除API酝润,也是在數(shù)據(jù)庫(kù)一層完成的操作燎竖。關(guān)于批處理的API
很多都是iOS8
、iOS9
出來(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完域,衷心感謝软吐!??