認識CoreData - 使用進階

該文章屬於<簡書 — 劉小壯>原創,轉載請註明:

<簡書 — 劉小壯> http://www.jianshu.com/p/a4710356244dandroid


以前兩篇文章都比較偏理論,文字表達比較多一些,但都是乾貨!學習時先理解理論知識,才能更好的幫助後面的理解。

在這篇文章中,將會涉及關於CoreData的一些複雜操做,這些操做會涉及分頁查詢、模糊查詢、批處理等高級操做。ios

經過這些操做能夠更好的使用CoreData,提高CoreData性能。文章中將會出現大量示例代碼,經過代碼的方式更有助於理解。
文章內容還會比較多,但願各位耐心看完。git

文章中若有疏漏或錯誤,還請各位及時提出,謝謝!😊github


佔位圖

NSPredicate

概述

iOS開發過程當中,不少需求都須要用到過濾條件。例如過濾一個集合對象中存儲的對象,能夠經過Foundation框架下的NSPredicate類來執行這個操做。正則表達式

CoreData中能夠經過設置NSFetchRequest類的predicate屬性,來設置一個NSPredicate類型的謂詞對象當作過濾條件。經過設置這個過濾條件,能夠只獲取符合過濾條件的託管對象,不會將全部託管對象都加載到內存中。這樣是很是節省內存和加快查找速度的,設計一個好的NSPredicate能夠優化CoreData搜索性能。數據庫

語法

NSPredicate更加偏向於天然語言,不像SQLite同樣有不少固定的語法,看起來也更加清晰易懂。例以下面須要查找條件爲年齡30歲以上,而且包括30歲的條件。express

[NSPredicate predicateWithFormat:@"age >= 30"]
過濾集合對象

能夠經過NSPredicateiOS中的集合對象執行過濾操做,能夠是NSArrayNSSet及其子類。數組

對不可變數組NSArray執行的過濾,過濾後會返回一個NSArray類型的結果數組,其中存儲着符合過濾條件的對象。緩存

NSArray *results = [array filteredArrayUsingPredicate:predicate]

對可變數組NSMutableArray執行的過濾條件,過濾後會直接改變原集合對象內部存儲的對象,刪除不符合條件的對象。安全

[arrayM filterUsingPredicate:predicate]
複合過濾條件

謂詞不僅能夠過濾簡單條件,還能夠過濾複雜條件,設置複合過濾條件。

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

固然也能夠經過NSCompoundPredicate對象來設置複合過濾條件,返回結果是一個NSPredicate的子類NSCompoundPredicate對象。

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

枚舉值NSCompoundPredicateType參數,能夠設置三種複合條件,枚舉值很是直觀很容易看懂。

  • NSNotPredicateType
  • NSAndPredicateType
  • NSOrPredicateType
基礎語法

下面是列舉的一些NSPredicate的基礎語法,這些語法看起來很是容易理解,更復雜的用法能夠去看蘋果的官方API

語法 做用
== 判斷是否相等
>= 大於或等於
<= 小於或等於
> 大於
< 小於
!= 不等於
AND 或 &&
OR 或 II
NOT 或 !
正則表達式

NSPredicate中還可使用正則表達式,能夠經過正則表達式完成一些複雜需求,這使得謂詞的功能更增強大,例以下面是一個手機號驗證的正則表達式

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

NSPredicate支持對數據的模糊查詢,例以下面使用通配符來匹配包含lxz的結果,具體CoreData中的使用在下面會講到。

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

NSPredicate在建立查詢條件時,還支持設置被匹配目標的keyPath,也就是設置更深層被匹配的目標。例以下面設置employeename屬性爲查找條件,就是用點語法設置的keyPath

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

設置查詢條件

在以前的文章中,執行下面MOCfetchRequest方法,通常都須要傳入一個NSFetchRequest類型的參數。這個request參數能夠作一些設置操做,這樣就能夠以較優的性能獲取指定的數據。

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

NSFetchRequest

在執行fetch操做前,能夠給NSFetchRequest設置一些參數,這些參數包括謂詞、排序等條件,下面是一些基礎的設置。

  • 設置查找哪一個實體,從數據庫的角度來看就是查找哪張表,經過fetchRequestWithEntityName:或初始化方法來指定表名。
  • 經過NSPredicate類型的屬性,能夠設置查找條件,這個屬性在開發中用得最多。NSPredicate能夠包括固定格式的條件以及正則表達式
  • 經過sortDescriptors屬性,能夠設置獲取結果數組的排序方式,這個屬性是一個數組類型,也就是能夠設置多種排序條件。(可是注意條件不要衝突)
  • 經過fetchOffset屬性設置從查詢結果的第幾個開始獲取,經過fetchLimit屬性設置每次獲取多少個。主要用於分頁查詢,後面會講。

MOC執行fetch操做後,獲取的結果是以數組的形式存儲的,數組中存儲的就是託管對象。NSFetchRequest提供了參數resultType,參數類型是一個枚舉類型。經過這個參數,能夠設置執行fetch操做後返回的數據類型。

  • NSManagedObjectResultType: 返回值是NSManagedObject的子類,也就是託管對象,這是默認選項。
  • NSManagedObjectIDResultType: 返回NSManagedObjectID類型的對象,也就是NSManagedObjectID,對內存佔用比較小。MOC能夠經過NSManagedObjectID對象獲取對應的託管對象,而且能夠經過緩存NSManagedObjectID參數來節省內存消耗。
  • NSDictionaryResultType: 返回字典類型對象。
  • NSCountResultType: 返回請求結果的count值,這個操做是發生在數據庫層級的,並不須要將數據加載到內存中。

設置獲取條件

// 創建獲取數據的請求對象,並指明操做Employee表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 設置請求條件,經過設置的條件,來過濾出須要的數據
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
request.predicate = predicate;

// 設置請求結果排序方式,能夠設置一個或一組排序方式,最後將全部的排序方式添加到排序數組中
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
// NSSortDescriptor的操做都是在SQLite層級完成的,不會將對象加載到內存中,因此對內存的消耗是很是小的
request.sortDescriptors = @[sort];

// 執行獲取請求操做,獲取的託管對象將會被存儲在一個數組中並返回
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);
}];

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

這裏設置NSFetchRequest對象的一些請求條件,設置查找Employee表中namelxz的數據,而且將全部符合的數據用height升序的方式排列。

有實體關聯關係

一個模型文件中的不一樣實體間,能夠設置實體間的關聯關係,這個在以前的文章中講過。實體關聯關係分爲對一對多,也能夠設置是否雙向關聯

這裏演示的實體只是簡單的To One的關係,而且下面會給出設置是否雙向關聯的區別對比。

插入實體

// 建立託管對象,並將其關聯到指定的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;

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

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

上面建立了四個實體,而且將Employee都關聯到Department上,完成關聯操做後經過MOC存儲到本地。

能夠看到上面全部的託管對象建立時,都使用NSEntityDescriptioninsert方法建立,並和上下文創建關係。這時就想問了,我能直接採用傳統的init方法建立嗎?

會崩的😱!建立託管對象時須要指定MOC,在運行時動態的生成setget方法。可是直接經過init方法初始化的對象,系統是不知道這裏是須要系統自身生成setget方法的,並且系統也不知道應該對應哪一個MOC,會致使方法未實現的崩潰。因此就出現了開發中常常出現的錯誤,以下面崩潰信息:

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

雙向關聯

在上一篇文章中提到過雙向關聯的概念,也就是設置RelationshipInverse是否爲空。下面是EmployeeDepartment在數據庫中,設置inverse和沒有設置inverse的兩種數據存儲,能夠很清晰的對比出設置雙向關聯的區別。
測試代碼仍是用上面插入實體的代碼,只是更改inverse選項。

設置雙向關聯

Employee

Department

未設置雙向關聯

Employee

Department

從圖中能夠看出,未設置雙向關聯的實體,Department關聯Employee爲屬性並存儲後,Department表中的關係是存在的,但Employee表中的關係依然是空的。而設置雙向關聯後的實體,在Department關聯Employee爲屬性並存儲後,Employee在表中自動設置了和Department的關係。

雙向關聯的關係不僅體如今數據庫中,在程序運行過程當中託管對象的關聯屬性,也是隨着發生變化的。雙向關聯的雙方,一方的關聯屬性設置關係後,另外一方關聯屬性的關係也會發生變化。用下面的代碼打印一下各自的關聯屬性,結果和上面數據庫的變化是同樣的。

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

查詢操做

// 建立獲取數據的請求對象,並指明操做Department表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Department"];

// 設置請求條件,設置employee的name爲請求條件。NSPredicate的好處在於,能夠設置keyPath條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"];
request.predicate = predicate;

// 執行查找操做
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);
}];

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

查找Department實體,並打印實體內容。就像上面講的雙向關係同樣,有關聯關係的實體,本身被查找出來後,也會將與之關聯的其餘實體也查找出來,而且查找出來的實體都是關聯着MOC的。

分頁查詢

在從本地存儲區獲取數據時,能夠指定從第幾個獲取,以及本次查詢獲取多少個數據,聯合起來使用就是分頁查詢。固然也能夠根據需求,單獨使用這兩個API

這種需求在實際開發中很是常見,例如TableView中,上拉加載數據,每次加載20條數據,就能夠利用分頁查詢輕鬆實現。

// 建立獲取數據的請求對象,並指明操做Employee表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 設置查找起始點,這裏是從搜索結果的第六個開始獲取
request.fetchOffset = 6;

// 設置分頁,每次請求獲取六個託管對象
request.fetchLimit = 6;

// 設置排序規則,這裏設置身高升序排序
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
request.sortDescriptors = @[descriptor];

// 執行查詢操做
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);
}];

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

上面是一個按照身高升序排序,分頁獲取搜索結果的例子。查找Employee表中的實體,將結果按照height字段升序排序,並從結果的第六個開始查找,而且設置獲取的數量也是六個。

模糊查詢

有時須要獲取具備某些相同特徵的數據,這樣就須要對查詢的結果作模糊匹配。在CoreData執行模糊匹配時,能夠經過NSPredicate執行這個操做。

// 建立獲取數據的請求對象,設置對Employee表進行操做
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 建立模糊查詢條件。這裏設置的帶通配符的查詢,查詢條件是結果包含lxz
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"];
request.predicate = predicate;

// 執行查詢操做
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);
}];

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

上面是使用通配符的方式進行模糊查詢NSPredicate支持多種形式的模糊查詢,下面列舉一些簡單的匹配方式。模糊查詢條件對大小寫不敏感,因此查詢條件大小寫都可。

  • 以lxz開頭

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

  • 以lxz結尾

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

  • 其中包含lxz

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

  • 查詢條件結果包含lxz

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

加載請求模板

在以前的文章中談到在模型文件中設置請求模板,也就是在.xcdatamodeld文件中,設置Fetch Requests,使用時能夠經過對應的NSManagedObjectModel獲取設置好的模板。

.... 省略上下文建立步驟 ....
// 經過MOC獲取模型文件對應的託管對象模型
NSManagedObjectModel *model = context.persistentStoreCoordinator.managedObjectModel;
// 經過.xcdatamodeld文件中設置的模板名,獲取請求對象
NSFetchRequest *fetchRequest = [model fetchRequestTemplateForName:@"EmployeeFR"];

// 請求數據,下面的操做和普通請求同樣
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]);
}];

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

獲取結果Count值

開發過程當中有時須要只獲取所需數據的Count值,也就是執行獲取操做後數組中所存儲的對象數量。遇到這個需求,若是像以前同樣MOC執行獲取操做,獲取到數組而後取Count,這樣對內存消耗是很大的

對於這個需求,蘋果提供了兩種經常使用的方式獲取這個Count值。這兩種獲取操做,都是在數據庫中完成的,並不須要將託管對象加載到內存中,對內存的開銷也是很小的。

方法1,設置resultType

// 設置過濾條件,能夠根據需求設置本身的過濾條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"];
// 建立請求對象,並指明操做Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
fetchRequest.predicate = predicate;
// 這一步是關鍵。設置返回結果類型爲Count,返回結果爲NSNumber類型
fetchRequest.resultType = NSCountResultType;

// 執行查詢操做,返回的結果仍是數組,數組中只存在一個對象,就是計算出的Count值
NSError *error = nil;
NSArray *dataList = [context executeFetchRequest:fetchRequest error:&error];
NSInteger count = [dataList.firstObject integerValue];
NSLog(@"fetch request result Employee.count = %ld", count);

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

方法1中設置NSFetchRequest對象的resultTypeNSCountResultType,獲取到結果的Count值。這個枚舉值在以前的文章中提到過,除了Count參數,還能夠設置其餘三種參數。

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

// 設置過濾條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < 2"];
// 建立請求對象,指明操做Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
fetchRequest.predicate = predicate;

// 經過調用MOC的countForFetchRequest:error:方法,獲取請求結果count值,返回結果直接是NSUInteger類型變量
NSError *error = nil;
NSUInteger count = [context countForFetchRequest:fetchRequest error:&error];
NSLog(@"fetch request result count is : %ld", count);

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

MOC提供了專門獲取請求結果Count值的方法,經過這個方法能夠直接返回一個NSUInteger類型的Count值,使用起來比上面的方法更方便點,其餘都是同樣的。

位運算

假設有需求是對Employee表中,全部託管對象的height屬性計算總和。這個需求在數據量比較大的狀況下,將全部託管對象加載到內存中是很是消耗內存的,就算批量加載也比較耗時耗內存。

CoreData對於這樣的需求,提供了位運算的功能。MOC在執行請求時,是支持對數據進行位運算的。這個操做依然是在數據庫層完成的,對內存的佔用很是小。

// 建立請求對象,指明操做Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 設置返回值爲字典類型,這是爲告終果能夠經過設置的name名取出,這一步是必須的
fetchRequest.resultType = NSDictionaryResultType;

// 建立描述對象
NSExpressionDescription *expressionDes = [[NSExpressionDescription alloc] init];
// 設置描述對象的name,最後結果須要用這個name當作key來取出結果
expressionDes.name = @"sumOperatin";
// 設置返回值類型,根據運算結果設置類型
expressionDes.expressionResultType = NSFloatAttributeType;

// 建立具體描述對象,用來描述對那個屬性進行什麼運算(可執行的運算類型不少,這裏描述的是對height屬性,作sum運算)
NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"height"]]];
// 只能對應一個具體描述對象
expressionDes.expression = expression;
// 給請求對象設置描述對象,這裏是一個數組類型,也就是能夠設置多個描述對象
fetchRequest.propertiesToFetch = @[expressionDes];

// 執行請求,返回值仍是一個數組,數組中只有一個元素,就是存儲計算結果的字典
NSError *error = nil;
NSArray *resultArr = [context executeFetchRequest:fetchRequest error:&error];
// 經過上面設置的name值,當作請求結果的key取出計算結果
NSNumber *number = resultArr.firstObject[@"sumOperatin"];
NSLog(@"fetch request result is %f", [number floatValue]);

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

執行結果

從執行結果能夠看到,MOC對全部查找到的託管對象height屬性執行了求和操做,並將結果放在字典中返回。位運算主要是經過NSFetchRequest對象的propertiesToFetch屬性設置,這個屬性能夠設置多個描述對象,最後經過不一樣的name當作key來取出結果便可。

NSExpression類能夠描述多種運算,能夠在NSExpression.h文件中的註釋部分,看到全部支持的運算類型,大概看了一下有二十多種運算。並且除了上面NSExpression調用的方法,此類還支持點語法的位運算,例以下面的例子。

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

批處理

在使用CoreData以前,我和公司同事也討論過,假設遇到須要大量數據處理的時候怎麼辦。CoreData對於大量數據處理的靈活性確定不如SQLite,這時候還須要本身使用其餘方式優化數據處理。雖然在移動端這種狀況不多出現,可是在持久層設計時仍是要考慮這方面。

當須要進行數據的處理時,CoreData須要先將數據加載到內存中,而後才能對數據進行處理。這樣對於大量數據來講,都加載到內存中是很是消耗內存的,並且容易致使崩潰的發生。若是遇到更改全部數據的某個字段這樣的簡單需求,須要將相關的託管對象都加載到內存中,而後進行更改、保存。

對於上面這樣的問題,CoreDataiOS8推出了批量更新API,經過這個API能夠直接在數據庫一層就完成更新操做,而不須要將數據加載到內存。除了批量更新操做,在iOS9中還推出了批量刪除API,也是在數據庫一層完成的操做。關於批處理的API不少都是iOS8iOS9出來的,使用時須要注意版本兼容

可是有個問題,批量更新和批量刪除的兩個API,都是直接對數據庫進行操做,更新完以後會致使MOC緩存和本地持久化數據不一樣步的問題。因此須要手動刷新受影響的MOC中存儲的託管對象,使MOC和本地統一。假設你使用了NSFetchedResultsController,爲了保證界面和數據的統一,這一步更新操做更須要作。

批量更新

// 建立批量更新對象,並指明操做Employee表。
NSBatchUpdateRequest *updateRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Employee"];
// 設置返回值類型,默認是什麼都不返回(NSStatusOnlyResultType),這裏設置返回發生改變的對象Count值
updateRequest.resultType = NSUpdatedObjectsCountResultType;
// 設置發生改變字段的字典
updateRequest.propertiesToUpdate = @{@"height" : [NSNumber numberWithFloat:5.f]};

// 執行請求後,返回值是一個特定的result對象,經過result的屬性獲取返回的結果。MOC的這個API是從iOS8出來的,因此須要注意版本兼容。
NSError *error = nil;
NSBatchUpdateResult *result = [context executeRequest:updateRequest error:&error];
NSLog(@"batch update count is %ld", [result.result integerValue]);

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

// 更新MOC中的託管對象,使MOC和本地持久化區數據同步
[context refreshAllObjects];

上面對Employee表中全部的託管對象height值作了批量更新,在更新時經過設置propertiesToUpdate字典來控制更新字段和更新的值,設置格式是字段名 : 新值。經過設置批處理對象的predicate屬性,設置一個謂詞對象來控制受影響的對象

還能夠對多個存儲區(數據庫)作一樣批處理操做,經過設置其父類affectedStores屬性,類型是一個數組,能夠包含受影響的存儲區,多個存儲區的操做對批量刪除一樣適用

MOC在執行請求方法時,發現方法名也不同了,執行的是executeRequest: error:方法,這個方法是從iOS8以後出來的。方法傳入的參數是NSBatchUpdateRequest類,此類並非繼承自NSFetchRequest類,而是直接繼承自NSPersistentStoreRequest,和NSFetchRequest是平級關係。

批量刪除

// 建立請求對象,並指明對Employee表作操做
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
// 經過謂詞設置過濾條件,設置條件爲height小於1.7
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"height < %f", 1.7f];
fetchRequest.predicate = predicate;

// 建立批量刪除請求,並使用上面建立的請求對象當作參數進行初始化
NSBatchDeleteRequest *deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest];
// 設置請求結果類型,設置爲受影響對象的Count
deleteRequest.resultType = NSBatchDeleteResultTypeCount;

// 使用NSBatchDeleteResult對象來接受返回結果,經過id類型的屬性result獲取結果
NSError *error = nil;
NSBatchDeleteResult *result = [context executeRequest:deleteRequest error:&error];
NSLog(@"batch delete request result count is %ld", [result.result integerValue]);

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

// 更新MOC中的託管對象,使MOC和本地持久化區數據同步
[context refreshAllObjects];

大多數狀況下,涉及到託管對象的操做,都須要將其加載到內存中完成。因此使用CoreData時,須要注意內存的使用,不要在內存中存在過多的託管對象。在已經作系統兼容的狀況下,進行大量數據的操做時,應該儘可能使用批處理來完成操做。

須要注意的是,refreshAllObjects是從iOS9出來的,在iOS9以前由於要作版本兼容,因此須要使用refreshObject: mergeChanges:方法更新託管對象。

異步請求

// 建立請求對象,並指明操做Employee表
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 建立異步請求對象,並經過一個block進行回調,返回結果是一個NSAsynchronousFetchResult類型參數
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);
    }];
}];

// 執行異步請求,和批量處理執行同一個請求方法
NSError *error = nil;
[context executeRequest:asycFetchRequest error:&error];

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

上面經過NSAsynchronousFetchRequest對象建立了一個異步請求,並經過block進行回調。若是有多個請求同時發起不須要擔憂線程安全的問題,系統會將全部的異步請求添加到一個操做隊列中,在前一個任務訪問數據庫時,CoreData會將數據庫加鎖,等前面的執行完成纔會繼續執行後面的操做。

NSAsynchronousFetchRequest提供了cancel方法,也就是能夠在請求過程當中,將這個請求取消。還能夠經過一個NSProgress類型的屬性,獲取請求完成進度。NSAsynchronousFetchRequest類從iOS8開始可使用,因此低版本須要作版本兼容。

須要注意的是,執行請求時MOC併發類型不能是NSConfinementConcurrencyType,這個併發類型已經被拋棄,會致使崩潰。


好多同窗都問我有Demo沒有,其實文章中貼出的代碼組合起來就是個Demo。後來想了想,仍是給本系列文章配了一個簡單的Demo,方便你們運行調試,後續會給全部博客的文章都加上Demo

Demo只是來輔助讀者更好的理解文章中的內容,應該博客結合Demo一塊兒學習,只看Demo仍是不能理解更深層的原理Demo中幾乎每一行代碼都會有註釋,各位能夠打斷點跟着Demo執行流程走一遍,看看各個階段變量的值。

Demo地址劉小壯的Github


這兩天更新了一下文章,將CoreData系列的六篇文章整合在一塊兒,作了一個PDF版的《CoreData Book》,放在我Github上了。PDF上有文章目錄,方便閱讀。

若是你以爲不錯,請把PDF幫忙轉到其餘羣裏,或者你的朋友,讓更多的人瞭解CoreData,衷心感謝!😁

相關文章
相關標籤/搜索