CoreData的結構構成:ios
NSManagedObjectModel的構成:git
能夠經過Entity
建立繼承自NSManagedObject
類的文件,這個文件就是開發中使用的託管對象,具有模型對象的表示功能,CoreData
的本地持久化都是經過這個類及其子類完成的。github
在CoreData
的總體結構中,主要分爲兩部分。一個是NSManagedObjectContext
管理的模型部分,管理着全部CoreData
的託管對象。一個是SQLite
實現的本地持久化部分,負責和SQL
數據庫進行數據交互,主要由NSPersistentStore
類操做。這就構成了CoreData
的大致結構。正則表達式
從圖中能夠看出,這兩部分都是比較獨立的,兩部分的交互由一個持久化存儲調度器(NSPersistentStoreCoordinator
)來控制。上層NSManagedObjectContext
存儲的數據都是交給持久化調度器,由調度器調用具體的持久化存儲對象(NSPersistentStore
)來操做對應的數據庫文件,NSPersistentStore
負責存儲的實現細節。這樣就很好的將兩部分實現了分離。算法
在模型文件的實體中,參數類型和平時建立繼承自NSObject
的模型類大致相似,可是仍是有一些關於類型的說明,下面簡單的列舉了一下。sql
Undefined: 默認值,參與編譯會報錯數據庫
Integer 16: 整數,表示範圍 -32768 ~ 32767
express
Integer 32: 整數,表示範圍 -2147483648 ~ 2147483647
數組
Integer 64: 整數,表示範圍 –9223372036854775808 ~ 9223372036854775807
緩存
Float: 小數,經過MAXFLOAT
宏定義來看,最大值用科學計數法表示是 0x1.fffffep+127f
Double: 小數,小數位比Float
更精確,表示範圍更大
String: 字符串,用NSString
表示
Boolean: 布爾值,用NSNumber
表示
Date: 時間,用NSDate
表示
Binary Data: 二進制,用NSData
表示
Transformable: OC
對象,用id
表示。能夠在建立託管對象類文件後,手動改成對應的OC
類名。使用的前提是,這個OC
對象必須遵照並實現NSCoding
協議
在實體最下面,有一個Fetched Properties
選項,這個選項用的很少,這裏就不細講了。Fetched Properties
用於定義查詢操做,和NSFetchRequest
功能相同。定義fetchedProperty
對象後,能夠經過NSManagedObjectModel
類的fetchRequestFromTemplateWithName:substitutionVariables:
方法或其餘相關方法獲取這個fetchedProperty
對象。
獲取這個對象後,系統會默認將這個對象緩存到一個字典中,緩存以後也能夠經過fetchedProperty
字典獲取fetchedProperty
對象。
屬性設置:
default Value: 設置默認值,除了二進制不能設置,其餘類型幾乎都能設置。
optional: 在使用時是否可選,也能夠理解爲若是設置爲NO
,只要向MOC
進行save
操做,這個屬性是否必須有值。不然MOC
進行操做時會失敗並返回一個error
,該選項默認爲YES
。
transient: 設置當前屬性是否只存在於內存,不被持久化到本地,若是設置爲YES
,這個屬性就不參與持久化操做,屬性的其餘操做沒有區別。transient
很是適合存儲一些在內存中緩存的數據,例如存儲臨時數據,這些數據每次都是不一樣的,並且不須要進行本地持久化,因此能夠聲明爲transient
的屬性。
indexed: 設置當前屬性是不是索引。添加索引後能夠有效的提高檢索操做的速度。可是對於刪除這樣的操做,刪除索引後其餘地方還須要作出相應的變化,因此速度會比較慢。
Validation: 經過Validation
能夠設置Max Value
和Min Value
,經過這兩個條件來約定數據,對數據的存儲進行一個驗證。數值類型都有相同的約定方式,而字符串則是約定長度,date
是約定時間。
Reg. Ex.(Regular Expression
): 能夠設置正則表達式,用來驗證和控制數據,不對數據自身產生影響。(只能應用於String
類型)
Allows External Storage: 當存儲二進制文件時,若是遇到比較大的文件,是否存儲在存儲區以外。若是選擇YES
,存儲文件大小超過1MB
的文件,都會存儲在存儲區以外。不然大型文件存儲在存儲區內,會形成SQLite
進行表操做時,效率受到影響。
Relationships設置:
delete rule: 定義關聯屬性的刪除規則。在當前對象和其餘對象有關聯關係時,當前對象被刪除後與之關聯對象的反應。這個參數有四個枚舉值,代碼對應着模型文件的相同選項。
NSNoActionDeleteRule 刪除後沒有任何操做,也不會將關聯對象的關聯屬性指向nil
。刪除後使用關聯對象的關聯屬性,可能會致使其餘問題。
NSNullifyDeleteRule 刪除後會將關聯對象的關聯屬性指向nil
,這是默認值。
NSCascadeDeleteRule 刪除當前對象後,會將與之關聯的對象也一併刪除。
NSDenyDeleteRule 在刪除當前對象時,若是當前對象還指向其餘關聯對象,則當前對象不能被刪除。
Type: 主要有兩種類型,To One
和To Many
,表示當前關係是一對多仍是一對一。
實體:
Parent Entity: 能夠在實體中建立繼承關係,在一個實體的菜單欄中經過Parent Entity
能夠設置父實體,這樣就存在了實體的繼承關係,最後建立出來的託管模型類也是具備繼承關係的。注意繼承關係中屬性名不要相同。
使用了這樣的繼承關係後,系統會將子類繼承父類的數據,存在父類的表中,全部繼承自同一父類的子類都會將父類部分存放在父類的表中。這樣可能會致使父類的表中數據量過多,形成性能問題。
二、CoreData-基礎使用
在實體最下面,有一個Fetched Properties
選項,這個選項用的很少,這裏就不細講了。Fetched Properties
用於定義查詢操做,和NSFetchRequest
功能相同。定義fetchedProperty
對象後,能夠經過NSManagedObjectModel
類的fetchRequestFromTemplateWithName:substitutionVariables:
方法或其餘相關方法獲取這個fetchedProperty
對象。
獲取這個對象後,系統會默認將這個對象緩存到一個字典中,緩存以後也能夠經過fetchedProperty
字典獲取fetchedProperty
對象。
在模型文件中Entities
下面有一個Fetch Requests
,這個也是配置請求對象的。可是這個使用起來更加直觀,能夠很容易的完成一些簡單的請求配置。相對於上面講到的Fetched Properties
,這個仍是更方便使用一些。
上面是對Employee
實體的height
屬性配置的Fetch Request
,這裏配置的height
要小於2米。配置以後能夠經過NSManagedObjectModel
類的fetchRequestTemplateForName:
方法獲取這個請求對象,參數是這個請求配置的名稱,也就是EmployeeFR
。
- (IBAction)SchoolAdd:(UIButton *)sender { // 建立託管對象,並指明建立的託管對象所屬實體名 _student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext]; _student.name = @"lxz"; // 實體中全部基礎數據類型,建立類文件後默認都是NSNumber類型的 _student.age = @(23); // 經過上下文保存對象,並在保存前判斷是否有更改 NSError * error = nil; if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) { BOOL isAddSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error]; if (isAddSuccess) { NSLog(@"SchoolisAddSuccess"); } } // 錯誤處理,能夠在這實現本身的錯誤處理邏輯 if (error) { NSLog(@"CoreData Insert Data Error : %@", error); } }
- (IBAction)SchoolDelete:(UIButton *)sender { // 創建獲取數據的請求對象,指明對Student實體進行刪除操做 NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 建立謂詞對象,過濾出符合要求的對象,也就是要刪除的對象 NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"]; // 執行獲取操做,找到要刪除的對象 NSError * error = nil; NSArray<Student *> * students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error]; // 遍歷符合刪除要求的對象數組,執行刪除操做 [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext deleteObject:obj]; }]; // 保存上下文,並判斷當前上下文是否有改動 if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) { BOOL isDeleteSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:nil]; if (isDeleteSuccess) { NSLog(@"SchoolisDeleteSuccess"); } } // 錯誤處理 if (error) { NSLog(@"CoreData Delete Data Error : %@", error); } }
- (IBAction)SchoolUpdate:(UIButton *)sender { // 創建獲取數據的請求對象,並指明操做的實體爲Student NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 建立謂詞對象,設置過濾條件 NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"]; request.predicate = predicate; // 執行獲取請求,獲取到符合要求的託管對象 NSError * error = nil; NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error]; // 遍歷獲取到的數組,並執行修改操做 [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { obj.age = @(24); }]; // 將上面的修改進行存儲 if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) { BOOL isUpdateSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:nil]; if (isUpdateSuccess) { NSLog(@"SchoolIsUpdateSuccess"); } } // 錯誤處理 if (error) { NSLog(@"CoreData Update Data Error : %@", error); } /** 在上面簡單的設置了NSPredicate的過濾條件,對於比較複雜的業務需求,還能夠設置複合過濾條件,例以下面的例子 [NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"] 也能夠經過NSCompoundPredicate對象來設置複合過濾條件 [[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:@[predicate1, predicate2]] */ }
- (IBAction)SchoolSearch:(UIButton *)sender { // 創建獲取數據的請求對象,指明操做的實體爲Student NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 執行獲取操做,獲取全部Student託管對象 NSError * error = nil; NSArray<Student *> * students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error]; // 遍歷輸出查詢結果 [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Student Name : %@, Age : %d", obj.name, obj.age); }]; // 錯誤處理 if (error) { NSLog(@"CoreData Ergodic Data Error : %@", error); } }
CoreData
中能夠經過設置NSFetchRequest
類的predicate
屬性,來設置一個NSPredicate
類型的謂詞對象當作過濾條件。經過設置這個過濾條件,能夠只獲取符合過濾條件的託管對象,不會將全部託管對象都加載到內存中。這樣是很是節省內存和加快查找速度的,設計一個好的NSPredicate
能夠優化CoreData
搜索性能。
[NSPredicate predicateWithFormat:@"age >= 30"]
能夠經過NSPredicate
對iOS
中的集合對象執行過濾操做,能夠是NSArray
、NSSet
及其子類。對不可變數組NSArray
執行的過濾,過濾後會返回一個NSArray
類型的結果數組,其中存儲着符合過濾條件的對象。
NSArray *results = [array filteredArrayUsingPredicate:predicate]
謂詞不僅能夠過濾簡單條件,還能夠過濾複雜條件,設置複合過濾條件。
[NSPredicate predicateWithFormat:@"(age < 25) AND (firstName = XiaoZhuang)"]
固然也能夠經過NSCompoundPredicate
對象來設置複合過濾條件,返回結果是一個NSPredicate
的子類NSCompoundPredicate
對象。
[[NSCompoundPredicate alloc] initWithType:NSAndPredicateType subpredicates:@[predicate1, predicate2]]
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*"]
NSPredicate
在建立查詢條件時,還支持設置被匹配目標的keyPath
,也就是設置更深層被匹配的目標。例以下面設置employee
的name
屬性爲查找條件,就是用點語法設置的keyPath
。
[NSPredicate predicateWithFormat:@"employee.name = %@", @"lxz"]
在執行fetch
操做前,能夠給NSFetchRequest
設置一些參數,這些參數包括謂詞、排序等條件,下面是一些基礎的設置。
fetchRequestWithEntityName:
或初始化方法來指定表名。NSPredicate
類型的屬性,能夠設置查找條件,這個屬性在開發中用得最多。NSPredicate
能夠包括固定格式的條件以及正則表達式。sortDescriptors
屬性,能夠設置獲取結果數組的排序方式,這個屬性是一個數組類型,也就是能夠設置多種排序條件。(可是注意條件不要衝突)fetchOffset
屬性設置從查詢結果的第幾個開始獲取,經過fetchLimit
屬性設置每次獲取多少個。主要用於分頁查詢,後面會講。 MOC
執行fetch
操做後,獲取的結果是以數組的形式存儲的,數組中存儲的就是託管對象。NSFetchRequest
提供了參數resultType
,參數類型是一個枚舉類型。經過這個參數,能夠設置執行fetch
操做後返回的數據類型。
// 創建獲取數據的請求對象,並指明操做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
值升序的方式排列。
// 建立獲取數據的請求對象,並指明操做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條數據,就能夠利用分頁查詢輕鬆實現。
#pragma mark - ----- Page && Fuzzy ------ //分頁查詢 - (IBAction)pageSearch:(UIButton *)sender { // 建立獲取數據的請求對象,並指明操做Student表 NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 設置查找起始點,這裏是從搜索結果的第六個開始獲取 request.fetchOffset = 6; // 設置分頁,每次請求獲取六個託管對象 request.fetchLimit = 6; // 設置排序規則,這裏設置年齡升序排序 NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]; request.sortDescriptors = @[descriptor]; // 執行查詢操做 NSError * error = nil; NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error]; // 遍歷輸出查詢結果 [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Page Search Result Name : %@, Age : %d", obj.name, obj.age); }]; // 錯誤處理 if (error) { NSLog(@"Page Search Data Error : %@", error); } }
上面是一個按照身高升序排序,分頁獲取搜索結果的例子。查找Employee
表中的實體,將結果按照height
字段升序排序,並從結果的第六個開始查找,而且設置獲取的數量也是六個。
有時須要獲取具備某些相同特徵的數據,這樣就須要對查詢的結果作模糊匹配。在CoreData
執行模糊匹配時,能夠經過NSPredicate
執行這個操做。
//模糊查詢 - (IBAction)fuzzySearch:(UIButton *)sender { // 建立獲取數據的請求對象,設置對Student表進行操做 NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 建立模糊查詢條件。這裏設置的帶通配符的查詢,查詢條件是結果包含lxz NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"]; request.predicate = predicate; // 執行查詢操做 NSError * error = nil; NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error]; // 遍歷輸出查詢結果 [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Fuzzy Search Result Name : %@, Age : %d", obj.name, obj.age); }]; // 錯誤處理 if (error) { NSLog(@"Fuzzy Search Data Error : %@", error); } /** 模糊查詢的關鍵在於設置模糊查詢條件,除了上面的模糊查詢條件,還能夠設置下面三種條件 */ // 以lxz開頭 // NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", @"lxz"]; // 以lxz結尾 // NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"name ENDSWITH %@" , @"lxz"]; // 其中包含lxz // NSPredicate *predicate3 = [NSPredicate predicateWithFormat:@"name contains %@" , @"lxz"]; // 還能夠設置正則表達式做爲查找條件,這樣使查詢條件更增強大,下面只是給了個例子 // NSString *mobile = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$"; // NSPredicate *predicate4 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", mobile]; }
上面是使用通配符的方式進行模糊查詢,NSPredicate
支持多種形式的模糊查詢,下面列舉一些簡單的匹配方式。模糊查詢條件對大小寫不敏感,因此查詢條件大小寫都可。
在以前的文章中談到在模型文件中設置請求模板,也就是在.xcdatamodeld
文件中,設置Fetch Requests
,使用時能夠經過對應的NSManagedObjectModel
獲取設置好的模板。
#pragma mark - ----- Fetch Request ------ /** 加載模型文件中設置的FetchRequest請求模板,模板名爲StudentAge,在School.xcdatamodeld中設置 */ - (IBAction)fetchRequest:(UIButton *)sender { // 經過MOC獲取託管對象模型,託管對象模型至關於.xcdatamodeld文件,存儲着.xcdatamodeld文件的結構 NSManagedObjectModel * model = [CoreDataManager sharedCoreDataManager].persistentContainer.managedObjectModel; // 經過.xcdatamodeld文件中設置的模板名,獲取請求對象 NSFetchRequest * fetchRequest = [model fetchRequestTemplateForName:@"StudentAge"]; // 請求數據,下面的操做和普通請求同樣 NSError *error = nil; NSArray<Student *> *dataList = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:fetchRequest error:&error]; // 遍歷獲取結果,並打印結果 [dataList enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Student.count = %ld, Student.age = %d", dataList.count, obj.age); }]; // 錯誤處理 if (error) { NSLog(@"Execute Fetch Request Error : %@", error); } }
/** 對請求結果進行排序 這個排序是發生在數據庫一層的,並非將結果取出後排序,因此效率比較高 */ - (IBAction)resultSort:(UIButton *)sender { // 創建獲取數據的請求對象,並指明操做Student表 NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 設置請求條件,經過設置的條件,來過濾出須要的數據 NSPredicate * predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"]; request.predicate = predicate; // 設置請求結果排序方式,能夠設置一個或一組排序方式,最後將全部的排序方式添加到排序數組中 NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]; // NSSortDescriptor的操做都是在SQLite層級完成的,不會將對象加載到內存中,因此對內存的消耗是很是小的 // 下面request的sort對象是一個數組,也就是能夠設置多種排序條件,但注意條件不要衝突 request.sortDescriptors = @[sort]; // 執行獲取請求操做,獲取的託管對象將會被存儲在一個數組中並返回 NSError * error = nil; NSArray<Student *> *students = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error]; // 遍歷返回結果 [students enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"Employee Name : %@, Age : %d", obj.name, obj.age); }]; // 錯誤處理 if (error) { NSLog(@"CoreData Fetch Data Error : %@", error); } }
開發過程當中有時須要只獲取所需數據的Count
值,也就是執行獲取操做後數組中所存儲的對象數量。遇到這個需求,若是像以前同樣MOC
執行獲取操做,獲取到數組而後取Count
,這樣對內存消耗是很大的。
對於這個需求,蘋果提供了兩種經常使用的方式獲取這個Count
值。這兩種獲取操做,都是在數據庫中完成的,並不須要將託管對象加載到內存中,對內存的開銷也是很小的。
/** 獲取返回結果的Count值,經過設置NSFetchRequest的resultType屬性 */ - (IBAction)getResultCount1:(UIButton *)sender { // 設置過濾條件,能夠根據需求設置本身的過濾條件 NSPredicate * predicate = [NSPredicate predicateWithFormat:@"age < 24"]; // 建立請求對象,並指明操做Student表 NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; fetchRequest.predicate = predicate; // 這一步是關鍵。設置返回結果類型爲Count,返回結果爲NSNumber類型 fetchRequest.resultType = NSCountResultType; // 執行查詢操做,返回的結果仍是數組,數組中只存在一個對象,就是計算出的Count值 NSError * error = nil; NSArray * dataList = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:fetchRequest error:&error]; // 返回結果存在數組的第一個元素中,是一個NSNumber的對象,經過這個對象便可得到Count值 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
參數,還能夠設置其餘三種參數。
- (IBAction)getResultCount2:(UIButton *)sender { // 設置過濾條件 NSPredicate * predicate = [NSPredicate predicateWithFormat:@"age < 24"]; // 建立請求對象,指明操做Student表 NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; fetchRequest.predicate = predicate; // 經過調用MOC的countForFetchRequest:error:方法,獲取請求結果count值,返回結果直接是NSUInteger類型變量 NSError * error = nil; NSUInteger count = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext 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
在執行請求時,是支持對數據進行位運算的。這個操做依然是在數據庫層完成的,對內存的佔用很是小。
/** 對返回的結果進行按位運算,這個運算是發生在SQLite數據庫層的,因此執行效率很快,對內存的消耗也很小 若是須要對託管對象的某個屬性進行運算,比較推薦這種效率高的方法. */ - (IBAction)bitwiseArithmetic:(UIButton *)sender { // 建立請求對象,指明操做Student表 NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 設置返回值爲字典類型,這是爲告終果能夠經過設置的name名取出,這一步是必須的 fetchRequest.resultType = NSDictionaryResultType; // 建立描述對象的name字符串 NSString * descriptionName = @"sumOperatin"; // 建立描述對象 NSExpressionDescription * expressionDes = [[NSExpressionDescription alloc] init]; // 設置描述對象的name,最後結果須要用這個name當作key來取出結果 expressionDes.name = descriptionName; // 設置返回值類型,根據運算結果設置類型 expressionDes.expressionResultType = NSInteger16AttributeType; // 建立具體描述對象,用來描述對哪一個屬性進行什麼運算(可執行的運算類型不少,這裏描述的是對age屬性,作sum運算) NSExpression * expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"age"]]]; // 只能對應一個具體描述對象 expressionDes.expression = expression; // 給請求對象設置描述對象,這裏是一個數組類型,也就是能夠設置多個描述對象 fetchRequest.propertiesToFetch = @[expressionDes]; // 執行請求,返回值仍是一個數組,數組中只有一個元素,就是存儲計算結果的字典 NSError * error = nil; NSArray * resultArr = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:fetchRequest error:&error]; // 經過上面設置的name值,當作請求結果的key取出計算結果 NSNumber * number = resultArr.firstObject[descriptionName]; NSLog(@"fetch request result is %ld", [number integerValue]); // 錯誤處理 if (error) { NSLog(@"fetch request result error : %@", error); } /** 位運算支持的算法種類不少,具體能夠在NSExpression.h文件中查看 */ }
從執行結果能夠看到,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
,爲了保證界面和數據的統一,這一步更新操做更須要作。
#pragma mark - ----- Batch Operation ------ /** 注意:不管是批量更新仍是批量刪除,這個批量操做都是發生在SQLite層的。然而在SQLite發生了批量操做後,並不會主動更新上層MOC中緩存的託管對象,因此在進行批量操做後,須要對相關的MOC進行更新操做。 雖然在客戶端不多遇到大量數據處理的狀況,可是若是遇到這樣的需求,推薦使用批量處理API。 */ /** 批量更新 */ - (IBAction)batchUpdate:(UIButton *)sender { // 建立批量更新對象,並指明操做Student表 NSBatchUpdateRequest * updateRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Student"]; // 設置返回值類型,默認是什麼都不返回(NSStatusOnlyResultType),這裏設置返回發生改變的對象Count值 updateRequest.resultType = NSUpdatedObjectsCountResultType; // 設置發生改變字段的字典 updateRequest.propertiesToUpdate = @{@"name" : @"lxz"}; // 執行請求後,返回值是一個特定的result對象,經過result的屬性獲取返回的結果。 // MOC的這個API是從iOS8出來的,因此須要注意版本兼容。 NSError * error = nil; NSBatchUpdateResult * result = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeRequest:updateRequest error:&error]; NSLog(@"batch update count is %ld", [result.result integerValue]); // 錯誤處理 if (error) { NSLog(@"batch update request result error : %@", error); } // 更新MOC中的託管對象,使MOC和本地持久化區數據同步 [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext refreshAllObjects]; }
上面對Employee
表中全部的託管對象height
值作了批量更新,在更新時經過設置propertiesToUpdate
字典來控制更新字段和更新的值,設置格式是字段名 : 新值
。經過設置批處理對象的predicate
屬性,設置一個謂詞對象來控制受影響的對象。
還能夠對多個存儲區(數據庫)作一樣批處理操做,經過設置其父類的affectedStores
屬性,類型是一個數組,能夠包含受影響的存儲區,多個存儲區的操做對批量刪除一樣適用。
MOC
在執行請求方法時,發現方法名也不同了,執行的是executeRequest: error:
方法,這個方法是從iOS8
以後出來的。方法傳入的參數是NSBatchUpdateRequest
類,此類並非繼承自NSFetchRequest
類,而是直接繼承自NSPersistentStoreRequest
,和NSFetchRequest
是平級關係。
/** 批量刪除 */ - (IBAction)batchDelete:(UIButton *)sender { // 建立請求對象,並指明對Student表作操做 NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 經過謂詞設置過濾條件,設置條件爲age小於20 NSPredicate * predicate = [NSPredicate predicateWithFormat:@"age < %ld", 20]; fetchRequest.predicate = predicate; // 建立批量刪除請求,並使用上面建立的請求對象當作參數進行初始化 NSBatchDeleteRequest * deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest]; // 設置請求結果類型,設置爲受影響對象的Count deleteRequest.resultType = NSBatchDeleteResultTypeCount; // 使用NSBatchDeleteResult對象來接受返回結果,經過id類型的屬性result獲取結果 NSError * error = nil; NSBatchDeleteResult * result = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext 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和本地持久化區數據同步 [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext refreshAllObjects]; }
大多數狀況下,涉及到託管對象的操做,都須要將其加載到內存中完成。因此使用CoreData
時,須要注意內存的使用,不要在內存中存在過多的託管對象。在已經作系統兼容的狀況下,進行大量數據的操做時,應該儘可能使用批處理來完成操做。
須要注意的是,refreshAllObjects
是從iOS9
出來的,在iOS9
以前由於要作版本兼容,因此須要使用refreshObject: mergeChanges:
方法更新託管對象。
#pragma mark - ----- Asynchronous Request ------ /** 異步處理 */ - (IBAction)asyncRequest:(UIButton *)sender { // 建立請求對象,並指明操做Student表 NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"]; // 建立異步請求對象,並經過一個block進行回調,返回結果是一個NSAsynchronousFetchResult類型參數 NSAsynchronousFetchRequest * asycFetchRequest = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult * _Nonnull result) { // 經過返回結果的finalResult屬性,獲取結果數組 [result.finalResult enumerateObjectsUsingBlock:^(Student * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"fetch request result Student.count = %ld, Student.name = %@", result.finalResult.count, obj.name); }]; }]; // 執行異步請求,和批量處理執行同一個請求方法 NSError * error = nil; [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeRequest:asycFetchRequest error:&error]; // 錯誤處理 if (error) { NSLog(@"fetch request result error : %@", error); } }
上面經過NSAsynchronousFetchRequest
對象建立了一個異步請求,並經過block
進行回調。若是有多個請求同時發起,不須要擔憂線程安全的問題,系統會將全部的異步請求添加到一個操做隊列中,在前一個任務訪問數據庫時,CoreData
會將數據庫加鎖,等前面的執行完成纔會繼續執行後面的操做。
NSAsynchronousFetchRequest
提供了cancel
方法,也就是能夠在請求過程當中,將這個請求取消。還能夠經過一個NSProgress
類型的屬性,獲取請求完成進度。NSAsynchronousFetchRequest
類從iOS8
開始可使用,因此低版本須要作版本兼容。
須要注意的是,執行請求時MOC
併發類型不能是NSConfinementConcurrencyType
,這個併發類型已經被拋棄,會致使崩潰。
在開發過程當中會常常用到UITableView
這樣的視圖類,這些視圖類須要本身管理其數據源,包括網絡獲取、本地存儲都須要寫代碼進行管理。
而在CoreData
中提供了NSFetchedResultsController
類(fetched results controller
,也叫FRC
),FRC
能夠管理UITableView
或UICollectionView
的數據源。這個數據源主要指本地持久化的數據,也能夠用這個數據源配合着網絡請求數據一塊兒使用,主要看業務需求了。
本篇文章會使用UITableView
做爲視圖類,配合NSFetchedResultsController
進行後面的演示,UICollectionView
配合NSFetchedResultsController
的使用也是相似,這裏就不都講了。
就像上面說到的,NSFetchedResultsController
就像是上面兩種視圖的數據管理者同樣。FRC
能夠監聽一個MOC
的改變,若是MOC
執行了託管對象的增刪改操做,就會對本地持久化數據發生改變,FRC
就會回調對應的代理方法,回調方法的參數會包括執行操做的類型、操做的值、indexPath
等參數。
實際使用時,經過FRC
「綁定」一個MOC
,將UITableView
嵌入在FRC
的執行流程中。在任何地方對這個「綁定」的MOC
存儲區作修改,都會觸發FRC
的回調方法,在FRC
的回調方法中嵌入UITableView
代碼並作對應修改便可。
由此能夠看出FRC
最大優點就是,始終和本地持久化的數據保持統一。只要本地持久化的數據發生改變,就會觸發FRC
的回調方法,從而在回調方法中更新上層數據源和UI
。這種方式講的簡單一點,就能夠叫作數據帶動UI。
可是須要注意一點,在FRC
的初始化中傳入了一個MOC
參數,FRC
只能監測傳入的MOC
發生的改變。假設其餘MOC
對同一個存儲區發生了改變,FRC
則不能監測到這個變化,不會作出任何反應。
因此使用FRC
時,須要注意FRC
只能對一個MOC
的變化作出反應,因此在CoreData
持久化層設計時,儘可能一個存儲區只對應一個MOC
,或設置一個負責UI
的MOC
,這在後面多線程部分會詳細講解。
在寫代碼以前,先對以前的模型文件結構作一些修改。
講FRC
的時候,只須要用到Employee
這一張表,其餘表和設置直接忽略。須要在Employee
原有字段的基礎上,增長一個String
類型的sectionName
字段,這個字段就是用來存儲section title
的,在下面的文章中將會詳細講到。
下面例子是比較經常使用的FRC
初始化方式,初始化時指定的MOC
,還用以前講過的MOC
初始化代碼,UITableView
初始化代碼這裏也省略了,主要突出FRC
的初始化。
#import "ChatViewController.h" #import "CoreDataManager.h" #import "User+CoreDataProperties.h" @interface ChatViewController ()<UITableViewDataSource, UITableViewDelegate,NSFetchedResultsControllerDelegate> @property (nonatomic, strong) CoreDataManager * manager; @property (nonatomic, strong) User * user; @property (nonatomic, strong) UITableView * tableView; @property (strong, nonatomic) NSFetchedResultsController * fetchedResultController; @end @implementation ChatViewController - (void)viewDidLoad { [super viewDidLoad]; _manager = [CoreDataManager sharedCoreDataManager]; _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, self.view.bounds.size.width, self.view.bounds.size.height-100) style:UITableViewStylePlain]; [self.view addSubview:_tableView]; _tableView.delegate = self; _tableView.dataSource = self; } #pragma mark - ----- UITableView Delegate ------ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.fetchedResultController.sections.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.fetchedResultController.sections[section].numberOfObjects; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { _user = [self.fetchedResultController objectAtIndexPath:indexPath]; UITableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:@"identifier" forIndexPath:indexPath]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"identifier"]; } cell.textLabel.text = _user.username; cell.detailTextLabel.text = _user.age; return cell; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return self.fetchedResultController.sections[section].indexTitle; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // 刪除託管對象 _user = [self.fetchedResultController objectAtIndexPath:indexPath]; [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext deleteObject:_user]; // 保存上下文環境,並作錯誤處理 NSError * error = nil; if (![[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error]) { NSLog(@"tableView delete cell error : %@", error); } } } #pragma mark - ----- NSFetchedResultsController ------ #pragma mark - ----- 生成測試數據 ------ //插入數據 - (IBAction)CreateTestData:(UIButton *)sender { for (int i = 0; i < 3; i++) { _user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext]; _user.username = [NSString stringWithFormat:@"username:%d", i]; _user.age = [NSString stringWithFormat:@"age:%d", i ]; _user.sectionName = [NSString stringWithFormat:@"sectionName:%d", i]; } NSError * error = nil; if ([CoreDataManager sharedCoreDataManager].persistentContainer.viewContext.hasChanges) { [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error]; } } - (IBAction)RefreshTestData:(UIButton *)sender { // 建立請求對象,並指明操做User表 NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"User"]; // 設置排序規則,指明根據age字段升序排序 NSSortDescriptor * ageSort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]; request.sortDescriptors = @[ageSort]; // 建立NSFetchedResultsController控制器實例,並綁定MOC NSError * error = nil; self.fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext sectionNameKeyPath:@"sectionName" cacheName:nil]; // 設置代理,並遵照協議 self.fetchedResultController.delegate = self; // 執行獲取請求,執行後FRC會從持久化存儲區加載數據,其餘地方能夠經過FRC獲取數據 [self.fetchedResultController performFetch:&error]; // 錯誤處理 if (error) { NSLog(@"NSFetchedResultsController init error : %@", error); } // 刷新UI [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"identifier"]; [self.tableView reloadData]; } #pragma mark - ----- NSFetchedResultsControllerDelegate ------ // Cell數據源發生改變會回調此方法,例如添加新的託管對象等 - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath { switch (type) { case NSFetchedResultsChangeInsert: [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeMove: [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeUpdate: { User * user = [self.fetchedResultController objectAtIndexPath:indexPath]; UITableViewCell * cell = [self.tableView cellForRowAtIndexPath:indexPath]; cell.textLabel.text = user.username; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } break; } } // Section數據源發生改變回調此方法,例如修改section title等 - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch (type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic]; break; default: break; } } // 本地數據源發生改變,將要開始回調FRC代理方法。 - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { [self.tableView beginUpdates]; } // 本地數據源發生改變,FRC代理方法回調完成。 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.tableView endUpdates]; } - (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName { return [NSString stringWithFormat:@"sectionName %@", sectionName]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. }
就像cellForRowAtIndexPath:
方法中使用的同樣,FRC
提供了兩個方法輕鬆轉換indexPath
和NSManagedObject
的對象,在實際開發中這兩個方法很是實用,這也是FRC
和UITableView
、UICollectionView
深度融合的表現。
- (id)objectAtIndexPath:(NSIndexPath *)indexPath; - (nullable NSIndexPath *)indexPathForObject:(id)object;
// Cell數據源發生改變會回調此方法,例如添加新的託管對象等 - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath { switch (type) { case NSFetchedResultsChangeInsert: [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeUpdate: { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; Employee *emp = [fetchedResultController objectAtIndexPath:indexPath]; cell.textLabel.text = emp.name; } break; } } // Section數據源發生改變回調此方法,例如修改section title等。 - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch (type) { case NSFetchedResultsChangeInsert: [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic]; break; default: break; } } // 本地數據源發生改變,將要開始回調FRC代理方法。 - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { [tableView beginUpdates]; } // 本地數據源發生改變,FRC代理方法回調完成。 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [tableView endUpdates]; } // 返回section的title,能夠在這裏對title作進一步處理。這裏修改title後,對應section的indexTitle屬性會被更新。 - (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName { return [NSString stringWithFormat:@"sectionName %@", sectionName]; }
上面就是當本地持久化數據發生改變後,被回調的FRC
代理方法的實現,能夠在對應的實現中完成本身的代碼邏輯。
在上面的章節中講到刪除cell
後,本地持久化數據同步的問題。在刪除cell
後在tableView
代理方法的回調中,調用了MOC
的刪除方法,使本地持久化存儲和UI
保持同步,並回調到下面的FRC
代理方法中,在代理方法中對UI
作刪除操做,這樣一套由UI的改變引起的刪除流程就完成了。
目前爲止已經實現了數據和UI
的雙向同步,即UI
發生改變後本地存儲發生改變,本地存儲發生改變後UI
也隨之改變。能夠經過下面添加數據的代碼來測試一下,NSFetchedResultsController
就講到這裏了。
- (void)addMoreData { Employee *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context]; employee.name = [NSString stringWithFormat:@"lxz 15"]; employee.height = @(15); employee.brithday = [NSDate date]; employee.sectionName = [NSString stringWithFormat:@"3"]; NSError *error = nil; if (![context save:&error]) { NSLog(@"MOC save error : %@", error); } }
CoreData
版本遷移的方式有不少,通常都是先在Xcode
中,原有模型文件的基礎上,建立一個新版本的模型文件,而後在此基礎上作不一樣方式的版本遷移。
本章節將會講三種不一樣的版本遷移方案,但都不會講太深,都是從使用的角度講起,能夠知足大多數版本遷移的需求。
在已經運行程序並經過模型文件生成數據庫後,再對模型文件進行的修改,若是隻是修改已有實體屬性的默認值、最大最小值、Fetch Request
等屬性自身包含的參數時,並不會發生錯誤。若是修改模型文件的結構,或修改屬性名、實體名等,形成模型文件的結構發生改變,這樣再次運行程序就會致使崩潰。
在開發測試過程當中,能夠直接將原有程序卸載就能夠解決這個問題,可是本地以前存儲的數據也會消失。若是是線上程序,就涉及到版本遷移的問題,不然會致使崩潰,並提示以下錯誤:
CoreData: error: Illegal attempt to save to a file that was never opened. "This NSPersistentStoreCoordinator has no persistent stores (unknown). It cannot perform a save operation.". No last error recorded.
然而在需求不斷變化的過程當中,後續版本確定會對原有的模型文件進行修改,這時就須要用到版本遷移的技術,下面開始講版本遷移的方案。
本文中講的幾種版本遷移方案,在遷移以前都須要對原有的模型文件建立新版本。
選中須要作遷移的模型文件 -> 點擊菜單欄Editor -> Add Model Version -> 選擇基於哪一個版本的模型文件(通常都是選擇目前最新的版本),新建模型文件完成。
對於新版本模型文件的命名,我在建立新版本模型文件時,通常會拿當前工程版本號當作後綴,這樣在模型文件版本比較多的時候,就能夠很容易將模型文件版本和工程版本對應起來。
添加完成後,會發現以前的模型文件會變成一個文件夾,裏面包含着多個模型文件。
在新建的模型文件中,裏面的文件結構和以前的文件結構相同。後續的修改都應該在新的模型文件上,以前的模型文件不要再動了,在修改完模型文件後,記得更新對應的模型類文件。
基於新的模型文件,對Employee
實體作以下修改,下面的版本遷移也以此爲例。
添加一個String
類型的屬性,設置屬性名爲sectionName
。
此時還應該選中模型文件,設置當前模型文件的版本。這裏選擇將最新版本設置爲剛纔新建的1.1.0版本
,模型文件設置工做完成。
Show The File Inspector -> Model Version -> Current 設置爲最新版本。
對模型文件的設置已經完成了,接下來系統還要知道咱們想要怎樣遷移數據。在遷移過程當中可能會存在多種可能,蘋果將這個靈活性留給了咱們完成。剩下要作的就是編寫遷移方案以及細節的代碼。
輕量級版本遷移方案很是簡單,大多數遷移工做都是由系統完成的,只須要告訴系統遷移方式便可。在持久化存儲協調器(PSC
)初始化對應的持久化存儲(NSPersistentStore
)對象時,設置options
參數便可,參數是一個字典。PSC
會根據傳入的字典,自動推斷版本遷移的過程。
NSMigratePersistentStoresAutomaticallyOption
設置爲YES
,CoreData
會試着把低版本的持久化存儲區遷移到最新版本的模型文件。NSInferMappingModelAutomaticallyOption
設置爲YES
,CoreData
會試着以最爲合理地方式自動推斷出源模型文件的實體中,某個屬性到底對應於目標模型文件實體中的哪個屬性。版本遷移的設置是在建立MOC
時給PSC
設置的,爲了使代碼更直觀,下面只給出發生變化部分的代碼,其餘MOC
的初始化代碼都不變。
// 設置版本遷移方案 NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : @YES, NSInferMappingModelAutomaticallyOption : @YES}; // 建立持久化存儲協調器,並將遷移方案的字典當作參數傳入 [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:options error:nil];
假設須要對已存在實體進行更名操做,須要將重命名後的實體Renaming ID
,設置爲以前的實體名。下面是Employee
實體進行操做。
修改後再使用實體時,應該將實體名設爲最新的實體名,這裏也就是Employee2
,並且數據庫中的數據也會遷移到Employee2
表中。
Employee2 *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee2" inManagedObjectContext:context]; emp.name = @"lxz"; emp.brithday = [NSDate date]; emp.height = @1.9; [context save:nil];
輕量級遷移方案只是針對增長和改變實體、屬性這樣的一些簡單操做,假設有更復雜的遷移需求,就應該使用Xcode
提供的遷移模板(Mapping Model
)。經過Xcode
建立一個後綴爲.xcmappingmodel
的文件,這個文件是專門用來進行數據遷移用的,一些變化關係也會體如今模板中,看起來很是直觀。
這裏還以上面更改實體名,並遷移實體數據爲例子,將Employee
實體遷移到Employee2
中。首先將Employee
實體更名爲Employee2
,而後建立Mapping Model
文件。
Command + N 新建文件 -> 選擇 Mapping Model -> 選擇源文件 Source Model -> 選擇目標文件 Target Model -> 命名 Mapping Model 文件名 -> Create 建立完成。
如今就建立好一個Mapping Model
文件,文件中顯示了實體、屬性、Relationships
,源文件和目標文件之間的關係。實體命名是EntityToEntity
的方式命名的,實體包含的屬性和關聯關係,都會被添加到遷移方案中(Entity Mapping
,Attribute Mapping
,Relationship Mapping
)。
在遷移文件的下方是源文件和目標文件的關係。
在上面圖中更名後的Employee2
實體並無遷移關係,因爲是更名後的實體,系統還不知道實體應該怎樣作遷移。因此選中Mapping Model
文件的Employee2 Mappings
,能夠看到右側邊欄的Source
爲invalid value
。由於要從Employee
實體遷移數據過來,因此將其選擇爲Employee
,遷移關係就設置完成了。
設置完成後,還應該將以前EmployeeToEmployee
的Mappings
刪除,由於這個實體已經被Employee2
替代,它的Mappings
也被Employee2 Mappings
所替代,不然會報錯。
在實體的遷移過程當中,還能夠經過設置Predicate
的方式,來簡單的控制遷移過程。例如只須要遷移一部分指定的數據,就能夠經過Predicate
來指定。能夠直接在右側Filter Predicate
的位置設置過濾條件,格式是$source.height < 100
,$source
表明數據源的實體。
若是還存在更復雜的遷移需求,並且上面的遷移方式不能知足,能夠考慮更復雜的遷移方式。假設要在遷移過程當中,對遷移的數據進行更改,這時候上面的遷移方案就不能知足需求了。
對於上面提到的問題,在Mapping Model
文件中選中實體,能夠看到Custom Policy
這個選項,選項對應的是NSEntityMigrationPolicy
的子類,能夠建立並設置一個子類,並重寫這個類的方法來控制遷移過程。
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error;
版本遷移在需求的變動中確定是要發生的,可是咱們應該儘可能避免這樣的狀況發生。在最開始設計模型文件數據結構的時候,就應該設計一個比較完善而且容易應對變化的結構,這樣後面就算髮生變化也不會對結構主體形成大的改動。
CoreData的多線程,其中會包括併發隊列類型、線程安全等技術點。
在CoreData
中MOC
是支持多線程的,能夠在建立MOC
對象時,指定其併發隊列的類型。當指定隊列類型後,系統會將操做都放在指定的隊列中執行,若是指定的是私有隊列,系統會建立一個新的隊列。但這都是系統內部的行爲,咱們並不能獲取這個隊列,隊列由系統所擁有,並由系統將任務派發到這個隊列中執行的。
init
方法初始化上下文,默認就是這個併發類型。這個枚舉值是不支持多線程的,從名字上也體現出來了。UI
相關的操做,應該考慮使用這個枚舉值初始化上下文。其中NSConfinementConcurrencyType
類型在iOS9
以後已經被蘋果廢棄,不建議使用這個API
。使用此類型建立的MOC
,調用某些比較新的CoreData
的API
可能會致使崩潰。
在CoreData
中MOC不是線程安全的,在多線程狀況下使用MOC
時,不能簡單的將MOC
從一個線程中傳遞到另外一個線程中使用,這並非CoreData
的多線程,並且會出問題。對於MOC
多線程的使用,蘋果給出了本身的解決方案。
在建立的MOC
中使用多線程,不管是私有隊列仍是主隊列,都應該採用下面兩種多線程的使用方式,而不是本身手動建立線程。調用下面方法後,系統內部會將任務派發到不一樣的隊列中執行。能夠在不一樣的線程中調用MOC
的這兩個方法,這個是容許的。
- (void)performBlock:(void (^)())block 異步執行的block,調用以後會馬上返回。 - (void)performBlockAndWait:(void (^)())block 同步執行的block,調用以後會等待這個任務完成,纔會繼續向下執行。
下面是多線程調用的示例代碼,在多線程的環境下執行MOC
的save
方法,就是將save
方法放在MOC
的block
體中異步執行,其餘方法的調用也是同樣的。
[context performBlock:^{
[context save:nil];
}];
可是須要注意的是,這兩個block
方法不能在NSConfinementConcurrencyType
類型的MOC
下調用,這個類型的MOC
是不支持多線程的,只支持其餘兩種併發方式的MOC
。
在業務比較複雜的狀況下,須要進行大量數據處理,而且還須要涉及到UI
的操做。對於這種複雜需求,若是都放在主隊列中,對性能和界面流暢度都會有很大的影響,致使用戶體驗很是差,下降屏幕FPS
。對於這種狀況,能夠採起多個MOC
配合的方式。
CoreData
多線程的發展中,在iOS5
經歷了一次比較大的變化,以後能夠更方便的使用多線程。從iOS5
開始,支持設置MOC
的parentContext
屬性,經過這個屬性能夠設置MOC
的父MOC
。下面會針對iOS5
以前和以後,分別講解CoreData
的多線程使用。
儘管如今的開發中早就不兼容iOS5
以前的系統了,可是做爲了解這裏仍是要講一下,並且這種同步方式在iOS5
以後也是能夠正常使用的,也有不少人還在使用這種同步方式,下面其餘章節也是同理。
在iOS5
以前實現MOC
的多線程,能夠建立多個MOC
,多個MOC
使用同一個PSC
,並讓多個MOC
實現數據同步。經過這種方式不用擔憂PSC
在調用過程當中的線程問題,MOC
在使用PSC
進行save
操做時,會對PSC
進行加鎖,等當前加鎖的MOC
執行完操做以後,其餘MOC
才能繼續執行操做。
每個PSC
都對應着一個持久化存儲區,PSC
知道存儲區中數據存儲的數據結構,而MOC
須要使用這個PSC
進行save
操做的實現。
這樣作有一個問題,當一個MOC
發生改變並持久化到本地時,系統並不會將其餘MOC
緩存在內存中的NSManagedObject
對象改變。因此這就須要咱們在MOC
發生改變時,將其餘MOC
數據更新。
根據上面的解釋,在下面例子中建立了一個主隊列的mainMOC
,主要用於UI
操做。一個私有隊列的backgroundMOC
,用於除UI
以外的耗時操做,兩個MOC
使用的同一個PSC
。
簡單狀況下的數據同步,是針對於只有一個MOC
的數據發生改變,並提交存儲區後,其餘MOC
更新時並無對相同的數據作改變,只是單純的同步數據的狀況。
在NSManagedObjectContext
類中,根據不一樣操做定義了一些通知。在一個MOC
發生改變時,其餘地方能夠經過MOC
中定義的通知名,來獲取MOC
發生的改變。在NSManagedObjectContext
中定義了下面三個通知:
MOC
將要向存儲區存儲數據時,調用這個通知。在這個通知中不能獲取發生改變相關的NSManagedObject
對象。MOC
向存儲區存儲數據後,調用這個通知。在這個通知中能夠獲取改變、添加、刪除等信息,以及相關聯的NSManagedObject
對象。MOC
中任何一個託管對象發生改變時,調用這個通知。例如修改託管對象的屬性。經過監聽NSManagedObjectContextDidSaveNotification
通知,獲取全部MOC
的save
操做。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(settingsContext:) name:NSManagedObjectContextDidSaveNotification object:nil];
不須要在通知的回調方法中,編寫代碼對比被修改的託管對象。MOC
爲咱們提供了下面的方法,只須要將通知對象傳入,系統會自動同步數據。
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification;
下面是通知中的實現代碼,可是須要注意的是,因爲通知是同步執行的,在通知對應的回調方法中所處的線程,和發出通知的MOC
執行操做時所處的線程是同一個線程,也就是系統performBlock:
回調方法分配的線程。
因此其餘MOC
在通知回調方法中,須要注意使用performBlock:
方法,並在block
體中執行操做。
- (void)settingsContext:(NSNotification *)noti { [context performBlock:^{ // 調用須要同步的MOC對象的merge方法,直接將通知對象當作參數傳進去便可,系統會完成同步操做。 [context mergeChangesFromContextDidSaveNotification:noti]; }]; }
在一個MOC
對本地存儲區的數據發生改變,而其餘MOC
也對一樣的數據作了改變,這樣後面執行save
操做的MOC
就會衝突,並致使後面的save
操做失敗,這就是複雜狀況下的數據合併。
這是由於每次一個MOC
執行一次fetch
操做後,會保存一個本地持久化存儲的狀態,當下次執行save
操做時會對比這個狀態和本地持久化狀態是否同樣。若是同樣,則表明本地沒有其餘MOC
對存儲發生過改變;若是不同,則表明本地持久化存儲被其餘MOC
改變過,這就是形成衝突的根本緣由。
對於這種衝突的狀況,能夠經過MOC
對象指定解決衝突的方案,經過mergePolicy
屬性來設置方案。mergePolicy
屬性有下面幾種可選的策略,默認是NSErrorMergePolicy
方式,這也是惟一一個有NSError
返回值的選項。
NSError
對象來描述錯誤,而MOC
和持久化存儲區不發生改變。MOC
的爲準,使用MOC
來覆蓋本地存儲的衝突部分。MOC
爲準,用MOC
的全部NSManagedObject
對象覆蓋本地存儲的對應對象。MOC
全部的NSManagedObject
對象被本地存儲的對應對象所覆蓋。上面五種策略中,除了第一個NSErrorMergePolicy
的策略,其餘四種中NSMergeByPropertyStoreTrumpMergePolicy
和NSRollbackMergePolicy
,以及NSMergeByPropertyObjectTrumpMergePolicy
和NSOverwriteMergePolicy
看起來是重複的。
其實它們並非衝突的,這四種策略的不一樣體如今,對沒有發生衝突的部分應該怎麼處理。NSMergeByPropertyStoreTrumpMergePolicy
和NSMergeByPropertyObjectTrumpMergePolicy
對沒有衝突的部分,未衝突部分數據並不會受到影響。而NSRollbackMergePolicy
和NSOverwriteMergePolicy
則是不管是否衝突,直接所有替換。
題外話:
對於MOC
的這種合併策略來看,有木有感受到CoreData
解決衝突的方式,和SVN
解決衝突的方式特別像。。。
不管是MOC
仍是託管對象,都不該該在其餘MOC
的線程中執行操做,這兩個API都不是線程安全的。但MOC
能夠在其餘MOC
線程中調用performBlock:
方法,切換到本身的線程執行操做。
若是其餘MOC
想要拿到託管對象,並在本身的隊列中使用託管對象,這是不容許的,託管對象是不能直接傳遞到其餘MOC
的線程的。可是能夠經過獲取NSManagedObject
的NSManagedObjectID
對象,在其餘MOC
中經過NSManagedObjectID
對象,從持久化存儲區中獲取NSManagedObject
對象,這樣就是容許的。NSManagedObjectID
是線程安全,而且能夠跨線程使用的。
能夠經過MOC
獲取NSManagedObjectID
對應的NSManagedObject
對象,例以下面幾個MOC
的API
。
NSManagedObject *object = [context objectRegisteredForID:objectID]; NSManagedObject *object = [context objectWithID:objectID];
經過NSManagedObject
對象的objectID
屬性,獲取NSManagedObjectID
類型的objectID
對象。
NSManagedObjectID *objectID = object.objectID;
上面章節中寫的大多都是怎麼用CoreData
多線程,在掌握多線程的使用後,就能夠根據公司業務需求,設計一套CoreData
多線程結構了。對於多線程結構的設計,應該本着儘可能減小主線程壓力的角度去設計,將全部耗時操做都放在子線程中執行。
對於具體的設計我根據不一樣的業務需求,給出兩種設計方案的建議。
在項目中多線程操做比較簡單時,能夠建立一個主隊列mainMOC
,和一個或多個私有隊列的backgroundMOC
。將全部backgroundMOC
的parentContext
設置爲mainMOC
,採起這樣的兩層設計通常就可以知足大多數需求了。
將耗時操做都放在backgroundMOC
中執行,mainMOC
負責全部和UI
相關的操做。全部和UI
無關的工做都交給backgroundMOC
,在backgroundMOC
對數據發生改變後,調用save
方法會將改變push
到mainMOC
中,再由mainMOC
執行save
方法將改變保存到存儲區。
代碼這裏就不寫了,和上面例子中設置parentContext
代碼同樣,主要講一下設計思路。
可是咱們發現,上面的save
操做最後仍是由mainMOC
去執行的,backgroundMOC
只是負責處理數據。雖然mainMOC
只執行save
操做並不會很耗時,可是若是save
涉及的數據比較多,這樣仍是會對性能形成影響的。
雖然客戶端不多涉及到大量數據處理的需求,可是假設有這樣的需求。能夠考慮在兩層結構之上,給mainMOC
之上再添加一個parentMOC
,這個parentMOC
也是私有隊列的MOC
,用於處理save
操做。
這樣CoreData
存儲的結構就是三層了,最底層是backgroundMOC
負責處理數據,中間層是mainMOC
負責UI
相關操做,最上層也是一個backgroundMOC
負責執行save
操做。這樣就將影響UI
的全部耗時操做全都剝離到私有隊列中執行,使性能達到了很好的優化。
須要注意的是,執行MOC
相關操做時,不要阻塞當前主線程。全部MOC
的操做應該是異步的,不管是子線程仍是主線程,儘可能少的使用同步block
方法。
設置MOC
的parentContext
屬性以後,parent
對於child
的改變是知道的,可是child
對於parent
的改變是不知道的。蘋果這樣設計,應該是爲了更好的數據同步。
Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:backgroundMOC]; emp.name = @"lxz"; emp.brithday = [NSDate date]; emp.height = @1.7f; [backgroundMOC performBlock:^{ [backgroundMOC save:nil]; [mainMOC performBlock:^{ [mainMOC save:nil]; }]; }];
在上面這段代碼中,mainMOC
是backgroundMOC
的parentContext
。在backgroundMOC
執行save
方法前,backgroundMOC
和mainMOC
都不能獲取到Employee
的數據,在backgroundMOC
執行完save
方法後,自身上下文發生改變的同時,也將改變push
到mainMOC
中,mainMOC
也具備了Employee
對象。
因此在backgroundMOC
的save
方法執行時,是對內存中的上下文作了改變,當擁有PSC
的mainMOC
執行save
方法後,是對本地存儲區作了改變。
CoreData
是蘋果自家推出的一個持久化框架,使用起來更加面向對象。可是在使用過程當中會出現大量代碼,並且CoreData
學習曲線比較陡峭,若是掌握很差,在使用過程當中很容易形成其餘問題。
國外開發者開源了一個基於CoreData
封裝的第三方——MagicalRecord
,就像是FMDB
封裝SQLite
同樣,MagicalRecord
封裝的CoreData
,使得原生的CoreData
更加容易使用。而且MagicalRecord
下降了CoreData
的使用門檻,不用去手動管理以前的PSC
、MOC
等對象。
根據Github
上MagicalRecord
的官方文檔,MagicalRecord
的優勢主要有三條:
1. 清理項目中CoreData
代碼
2. 支持清晰、簡單、一行式的查詢操做
3. 當須要優化請求時,能夠獲取NSFetchRequest
進行修改
將MagicalRecord
添加到項目中,和使用其餘第三方同樣,能夠經過下載源碼和CocoaPods
兩種方式添加。
1. 從Github下載MagicalRecord源碼,將源碼直接拖到項目中,後續須要手動更新源碼。
2. 也能夠經過CocoaPods
安裝MagicalRecord
,須要在Podfile
中加入下面命令,後續只須要經過命令來更新。
pod "MagicalRecord"
在以前建立新項目時,經過勾選"Use Core Data"
的方式添加CoreData
到項目中,會在AppDelegate
文件中生成大量CoreData
相關代碼。若是是大型項目,被佔用的位置是很重要的。而對於MagicalRecord
來講,只須要兩行代碼便可。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 初始化CoreData堆棧,也能夠指定初始化某個CoreData堆棧 [MagicalRecord setupCoreDataStack]; return YES; } - (void)applicationWillTerminate:(UIApplication *)application { // 在應用退出時,應該調用cleanUp方法 [MagicalRecord cleanUp]; }
MagicalRecord
是支持CoreData
的.xcdatamodeld
文件的,使得CoreData
這一優勢能夠繼續使用。創建數據結構時仍是像以前使用CoreData
同樣,經過.xcdatamodeld
文件的方式創建。
CoreData
是支持iCloud
的,MagicalRecord
對iCloud
相關的操做也作了封裝,只須要使用MagicalRecord+iCloud.h
類中提供的方法,就能夠進行iCloud
相關的操做。
例以下面是MagicalRecord+iCloud.h
中的一個方法,須要將相關參數傳入便可。
+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID localStoreNamed:(NSString *)localStore;
MagicalRecord
對上下文的管理和建立也比較全面,下面是MagicalRecord
提供的部分建立和獲取上下文的代碼。由於是給NSManagedObjectContext
添加的Category
,能夠直接用NSManagedObjectContext
類調用,使用很是方便。
可是須要注意,雖然系統幫咱們管理了上下文對象,對於耗時操做仍然要放在後臺線程中處理,而且在主線程中進行UI操做。
+ [NSManagedObjectContext MR_context] 設置默認的上下文爲它的父級上下文,併發類型爲NSPrivateQueueConcurrencyType + [NSManagedObjectContext MR_newMainQueueContext] 建立一個新的上下文,併發類型爲NSMainQueueConcurrencyType + [NSManagedObjectContext MR_newPrivateQueueContext] 建立一個新的上下文,併發類型爲NSPrivateQueueConcurrencyType + [NSManagedObjectContext MR_contextWithParent:] 建立一個新的上下文,容許自定義父級上下文,併發類型爲NSPrivateQueueConcurrencyType + [NSManagedObjectContext MR_contextWithStoreCoordinator:] 建立一個新的上下文,並容許自定義持久化存儲協調器,併發類型爲NSPrivateQueueConcurrencyType + [NSManagedObjectContext MR_defaultContext] 獲取默認上下文對象,項目中最基礎的上下文對象,併發類型是NSMainQueueConcurrencyType
MagicalRecord
對NSManagedObject
添加了一個Category
,將增刪改查等操做放在這個Category
中,使得這些操做能夠直接被NSManagedObject
類及其子類調用。
.1. 增
對於託管模型的建立很是簡單,不須要像以前還須要進行上下文的操做,如今這都是MagicalRecord
幫咱們完成的。
// 建立並插入到上下文中 Employee *emp = [Employee MR_createEntity];
.2. 刪
// 從上下文中刪除當前對象 [emp MR_deleteEntity];
.3. 改
// 獲取一個上下文對象 NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext]; // 在當前上下文環境中建立一個新的Employee對象 Employee *emp = [Employee MR_createEntityInContext:defaultContext]; emp.name = @"lxz"; emp.brithday = [NSDate date]; emp.height = @1.7; // 保存修改到當前上下文中 [defaultContext MR_saveToPersistentStoreAndWait];
.4. 查
// 執行查找操做,並設置排序條件 NSArray *empSorted = [Employee MR_findAllSortedBy:@"height" ascending:YES];
下面示例代碼中,Employee
根據已有的employeeFilter
謂詞對象,建立了employeeRequest
請求對象,並將請求對象作修改後,從MOC
中獲取請求結果,實現自定義查找條件。
NSPredicate *employeeFilter = [NSPredicate predicateWithFormat:@"name LIKE %@", @"*lxz*"]; NSFetchRequest *employeeRequest = [Employee MR_requestAllWithPredicate:employeeFilter]; employeeRequest.fetchOffset = 10; employeeRequest.fetchLimit = 10; NSArray *employees = [Employee MR_executeFetchRequest:employeeRequest];
.1. 能夠經過修改MR_LOGGING_DISABLED
預編譯指令的值,控制log
打印。
#define MR_LOGGING_DISABLED 1
.2.MagicalRecord
在DEBUG
模式下,對模型文件發生了更改,而且沒有建立新的模型文件版本。MagicalRecord
默認會將舊的持久化存儲刪除,建立新的持久化存儲。
MagicalRecord
的使用方法還有不少,這裏只是將一些比較經常使用的拿出來說講,其餘就不一一講解了。在Github
上有國人翻譯的MagicalRecord
官方文檔,翻譯的很是全面,並且是實時更新的。