該文章屬於<簡書 — 劉小壯>原創,轉載請註明:
<簡書 — 劉小壯> http://www.jianshu.com/p/a4710356244d
之前兩篇文章都比較偏理論,文字表達比較多一些,但都是乾貨!學習時先理解理論知識,才能更好的幫助後面的理解。在這篇文章中,將會涉及關於
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。
語法 | 作用 |
---|---|
== | 判斷是否相等 |
>= | 大於或等於 |
<= | 小於或等於 |
> | 大於 |
< | 小於 |
!= | 不等於 |
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
,也就是設置更深層被匹配的目標。例如下面設置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<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
表中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
選項。
設置雙向關聯
未設置雙向關聯
從圖中可以看出,未設置雙向關聯的實體,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
對象的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);
}
執行結果
從執行結果可以看到,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
,這個併發類型已經被拋棄,會導致崩潰。
好多同學都問我有Demo
沒有,其實文章中貼出的代碼組合起來就是個Demo
。後來想了想,還是給本系列文章配了一個簡單的Demo
,方便大家運行調試,後續會給所有博客的文章都加上Demo
。
Demo
只是來輔助讀者更好的理解文章中的內容,應該博客結合Demo
一起學習,只看Demo
還是不能理解更深層的原理。Demo
中幾乎每一行代碼都會有註釋,各位可以打斷點跟着Demo
執行流程走一遍,看看各個階段變量的值。
Demo地址:劉小壯的Github
這兩天更新了一下文章,將CoreData
系列的六篇文章整合在一起,做了一個PDF
版的《CoreData Book》,放在我Github上了。PDF
上有文章目錄,方便閱讀。
如果你覺得不錯,請把PDF幫忙轉到其他羣裏,或者你的朋友,讓更多的人瞭解CoreData,衷心感謝!😁