天天看點

iOS 認識CoreData-進階

轉自:http://www.cocoachina.com/ios/20160802/17260.html

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

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

NSPredicate

概述

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

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

文法

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

1

[NSPredicate predicateWithFormat:@

"age >= 30"

]

過濾集合對象

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

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

1

NSArray *results = [array filteredArrayUsingPredicate:predicate]

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

1

[arrayM filterUsingPredicate:predicate]

複合過濾條件

謂詞不隻可以過濾簡單條件,還可以過濾複雜條件,設定複合過濾條件。

1

[NSPredicate predicateWithFormat:@

"(age < 25) AND (firstName = XiaoZhuang)"

]

當然也可以通過NSCompoundPredicate對象來設定複合過濾條件,傳回結果是一個NSPredicate的子類NSCompoundPredicate對象。

1

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

枚舉值NSCompoundPredicateType參數,可以設定三種複合條件,枚舉值非常直覺很容易看懂。

  • NSNotPredicateType
  • NSAndPredicateType
  • NSOrPredicateType

基礎文法

下面是列舉的一些NSPredicate的基礎文法,這些文法看起來非常容易了解,更複雜的用法可以去看蘋果的官方API。

iOS 認識CoreData-進階

正規表達式

NSPredicate中還可以使用正規表達式,可以通過正規表達式完成一些複雜需求,這使得謂詞的功能更加強大,例如下面是一個手機号驗證的正規表達式。

1 2

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中的使用在下面會講到。

1

[NSPredicate predicateWithFormat:@

"name LIKE %@"

, @

"*lxz*"

]

keyPath

NSPredicate在建立查詢條件時,還支援設定被比對目标的keyPath,也就是設定更深層被比對的目标。例如下面設定employee的name屬性為查找條件,就是用點文法設定的keyPath。

1

[NSPredicate predicateWithFormat:@

"employee.name = %@"

, @

"lxz"

]

設定查詢條件

在之前的文章中,執行下面MOC的fetchRequest方法,一般都需要傳入一個NSFetchRequest類型的參數。這個request參數可以做一些設定操作,這樣就可以以較優的性能擷取指定的資料。

1

- (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值,這個操作是發生在資料庫層級的,并不需要将資料加載到記憶體中

設定擷取條件

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// 建立擷取資料的請求對象,并指明操作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的關系,并且下面會給出設定是否雙向關聯的差別對比。

插入實體

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// 建立托管對象,并将其關聯到指定的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,會導緻方法未實作的崩潰。是以就出現了開發中經常出現的錯誤,如下面崩潰資訊:

1

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

雙向關聯

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

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

設定雙向關聯

iOS 認識CoreData-進階

Employee

iOS 認識CoreData-進階

Department

未設定雙向關聯

iOS 認識CoreData-進階

Employee

iOS 認識CoreData-進階

Department

從圖中可以看出,未設定雙向關聯的實體,Department關聯Employee為屬性并存儲後,Department表中的關系是存在的,但Employee表中的關系依然是空的。而設定雙向關聯後的實體,在Department關聯Employee為屬性并存儲後,Employee在表中自動設定了和Department的關系。

雙向關聯的關系不隻展現在資料庫中,在程式運作過程中托管對象的關聯屬性,也是随着發生變化的。雙向關聯的雙方,一方的關聯屬性設定關系後,另一方關聯屬性的關系也會發生變化。用下面的代碼列印一下各自的關聯屬性,結果和上面資料庫的變化是一樣的。

1

NSLog(@

"Department : %@, Employee : %@"

, androidDepartment.employee, lsEmployee.department);

查詢操作

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// 建立擷取資料的請求對象,并指明操作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條資料,就可以利用分頁查詢輕松實作。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// 建立擷取資料的請求對象,并指明操作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執行這個操作。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// 建立擷取資料的請求對象,設定對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開頭
1

NSPredicate *predicate = [NSPredicate predicateWithFormat:@

"name BEGINSWITH %@"

, @

"lxz"

];

  • 以lxz結尾
1

NSPredicate *predicate = [NSPredicate predicateWithFormat:@

"name ENDSWITH %@"

, @

"lxz"

];

  • 其中包含lxz
1

NSPredicate *predicate = [NSPredicate predicateWithFormat:@

"name contains %@"

, @

"lxz"

];

  • 查詢條件結果包含lxz
1

NSPredicate *predicate = [NSPredicate predicateWithFormat:@

"name LIKE %@"

, @

"*lxz*"

];

加載請求模闆

在之前的文章中談到在模型檔案中設定請求模闆,也就是在.xcdatamodeld檔案中,設定Fetch Requests,使用時可以通過對應的NSManagedObjectModel擷取設定好的模闆。

.... 省略上下文建立步驟 ....

1 2 3 4 5 6 7 8 9 10 11 12 13 14

// 通過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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// 設定過濾條件,可以根據需求設定自己的過濾條件

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提供的方法
1 2 3 4 5 6 7 8 9 10 11 12 13

// 設定過濾條件

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在執行請求時,是支援對資料進行位運算的。這個操作依然是在資料庫層完成的,對記憶體的占用非常小。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

// 建立請求對象,指明操作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);

}

執行結果:

iOS 認識CoreData-進階

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

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

1

[NSExpression expressionWithFormat:@

"@sum.height"

];

批處理

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

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

對于上面這樣的問題,CoreData在iOS8推出了批量更新API,通過這個API可以直接在資料庫一層就完成更新操作,而不需要将資料加載到記憶體。除了批量更新操作,在iOS9中還推出了批量删除API,也是在資料庫一層完成的操作。關于批處理的API很多都是iOS8、iOS9出來的,使用時需要注意版本相容。

但是有個問題,批量更新和批量删除的兩個API,都是直接對資料庫進行操作,更新完之後會導緻MOC緩存和本地持久化資料不同步的問題。是以需要手動重新整理受影響的MOC中存儲的托管對象,使MOC和本地統一。假設你使用了NSFetchedResultsController,為了保證界面和資料的統一,這一步更新操作更需要做。

批量更新

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

// 建立批量更新對象,并指明操作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是平級關系。

批量删除

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

// 建立請求對象,并指明對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:方法更新托管對象。

異步請求

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// 建立請求對象,并指明操作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,這個并發類型已經被抛棄,會導緻崩潰。