你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 認識CoreData—使用進階

認識CoreData—使用進階

編輯:IOS開發基礎

1470038820892919.jpg

投稿文章,作者:劉小壯

之前兩篇文章都比較偏理論,文字表達比較多一些,但都是干貨!學習時先理解理論知識,才能更好的幫助後面的理解。在這篇文章中,將會涉及關於CoreData的一些復雜操作,這些操作會涉及分頁查詢、模糊查詢、批處理等高級操作。通過這些操作可以更好的使用CoreData,提升CoreData性能。文章中將會出現大量示例代碼,通過代碼的方式更有助於理解。

文章內容還會比較多,希望各位耐心看完。文章中如有疏漏或錯誤,還請各位及時提出,謝謝!

NSPredicate

概述

在iOS開發過程中,很多需求都需要用到過濾條件。例如過濾一個集合對象中存儲的對象,可以通過Foundation框架下的NSPredicate類來執行這個操作。

CoreData中可以通過設置NSFetchRequest類的predicate屬性,來設置一個NSPredicate類型的謂詞對象當做過濾條件。通過設置這個過濾條件,可以只獲取符合過濾條件的托管對象,不會將所有托管對象都加載到內存中。這樣是非常節省內存和加快查找速度的,設計一個好的NSPredicate可以優化CoreData搜索性能。

語法

NSPredicate更加偏向於自然語言,不像SQLite一樣有很多固定的語法,看起來也更加清晰易懂。例如下面需要查找條件為年齡30歲以上,並且包括30歲的條件。

[NSPredicate predicateWithFormat:@"age >= 30"]

過濾集合對象

可以通過NSPredicate對iOS中的集合對象執行過濾操作,可以是NSArray、NSSet及其子類。

對不可變數組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。

1470038261561323.png

正則表達式

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,也就是設置更深層被匹配的目標。例如下面設置employee的name屬性為查找條件,就是用點語法設置的keyPath。

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

設置查詢條件

在之前的文章中,執行下面MOC的fetchRequest方法,一般都需要傳入一個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類型的對象,也就是NSManagedObject的ID,對內存占用比較小。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 *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表中name為lxz的數據,並且將所有符合的數據用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存儲到本地。

可以看到上面所有的托管對象創建時,都使用NSEntityDescription的insert方法創建,並和上下文建立關系。這時就想問了,我能直接采用傳統的init方法創建嗎?

會崩的!創建托管對象時需要指定MOC,在運行時動態的生成set、get方法。但是直接通過init方法初始化的對象,系統是不知道這裡是需要系統自身生成set、get方法的,而且系統也不知道應該對應哪個MOC,會導致方法未實現的崩潰。所以就出現了開發中經常出現的錯誤,如下面崩潰信息:

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

雙向關聯

在上一篇文章中提到過雙向關聯的概念,也就是設置Relationship時Inverse是否為空。下面是Employee和Department在數據庫中,設置inverse和沒有設置inverse的兩種數據存儲,可以很清晰的對比出設置雙向關聯的區別。

測試代碼還是用上面插入實體的代碼,只是更改inverse選項。

設置雙向關聯

1470038393546128.png

Employee

1470038410803183.png

Department

未設置雙向關聯

1470038427903839.png

Employee

1470038444710264.png

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 *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 *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 *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 *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對象的resultType為NSCountResultType,獲取到結果的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);
}

執行結果:

270478-220387c54957ffe6.png

從執行結果可以看到,MOC對所有查找到的托管對象height屬性執行了求和操作,並將結果放在字典中返回。位運算主要是通過NSFetchRequest對象的propertiesToFetch屬性設置,這個屬性可以設置多個描述對象,最後通過不同的name當做key來取出結果即可。

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

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

批處理

在使用CoreData之前,我和公司同事也討論過,假設遇到需要大量數據處理的時候怎麼辦。CoreData對於大量數據處理的靈活性肯定不如SQLite,這時候還需要自己使用其他方式優化數據處理。雖然在移動端這種情況很少出現,但是在持久層設計時還是要考慮這方面。

當需要進行數據的處理時,CoreData需要先將數據加載到內存中,然後才能對數據進行處理。這樣對於大量數據來說,都加載到內存中是非常消耗內存的,而且容易導致崩潰的發生。如果遇到更改所有數據的某個字段這樣的簡單需求,需要將相關的托管對象都加載到內存中,然後進行更改、保存。

對於上面這樣的問題,CoreData在iOS8推出了批量更新API,通過這個API可以直接在數據庫一層就完成更新操作,而不需要將數據加載到內存。除了批量更新操作,在iOS9中還推出了批量刪除API,也是在數據庫一層完成的操作。關於批處理的API很多都是iOS8、iOS9出來的,使用時需要注意版本兼容。

但是有個問題,批量更新和批量刪除的兩個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,這個並發類型已經被拋棄,會導致崩潰。

相關閱讀:

認識CoreData—初識CoreData

認識CoreData-基礎使用

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved