MagicalRecord,一個簡化CoreData操作的工具庫
簡介
- 項目主頁: https://github.com/magicalpanda/MagicalRecord
- 實例下載: https://github.com/ios122/MagicalRecord </ul>
- 清理我的Core Data相關代碼
- 支持清晰,簡單,一行代碼式的查詢
- 當需要優化請求時,仍然可以修改 NSFetchRequest.
-
把下面一行添加到Podfile:
pod "MagicalRecord"
- 在工程目錄執行:pod update(國內推薦使用pod update --verbose --no-repo-update)
-
現在你可以添加#import <MagicalRecord/MagicalRecord.h>到任意項目源文件中,并開始使用MagicalRecord!
- 創建一個新的數據模型,命名為TestModel(File --> New --> File-->Core Data > Data Model)
- 添加一個新的實體,名為Person(Add Entity)
- 添加屬性age (Integer16), firstname (String)和 lastname (String)
4.創建 NSManagedObject (Editor > Create NSManagedObject Subclass… > Create)子類以更好地管理我們的實體
在軟件工程中,活動記錄模式是一種用于在關系數據庫中存儲數據的設計模式.這種設計模式最早由Martin Fowler在他的 Patterns of Enterprise Application Architecture 一書中命名.這樣的一個對象的,接口應該包含插入,更新和刪除的方法;再加上與底層數據庫幾乎直接對應的的屬性.
活動記錄是一種訪問數據庫中數據的方式.一個數據庫的表或者試圖被裝箱進一個類中;因此,一個對象實例對應表中的一行數據.在創建對象之后,會往表中添加新的一行以保存數據.加載對象時,從數據庫中獲取信息;當對象更新時,表中對應的行也會被更新.裝箱類實現存取方法和分別對應表或視圖中每一列的屬性.
MagicalRecord 受Ruby on Rails活動記錄獲取方式的便利性影響.項目目標是:
使用 CocoaPods 安裝
定義我們的數據模型
以定義實體 "Person"為例,它有屬性age, firstname和lastname.
Core Data的初始化與清理
如果在創建工程之初勾選了使用Core Data的選項,系統會自動在AppDelegate中生成大量的Core Data初始化與清理代碼.但是那些完全各使用一行代碼代替,如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [MagicalRecord setupCoreDataStack]; // ... return YES; } - (void)applicationWillTerminate:(UIApplication *)application { [MagicalRecord cleanUp]; }
代碼解讀
首先,在需要使用MagicalRecord的地方,引入頭文件:
#import <MagicalRecord/MagicalRecord.h>
然后,在你的App代理,- applicationDidFinishLaunching: withOptions:方法, 或-awakeFromNib方法中,使用下面方法中的 一種 來初始化 ** MagicalRecord** 類的調用:
+ (void)setupCoreDataStack; + (void)setupAutoMigratingCoreDataStack; + (void)setupCoreDataStackWithInMemoryStore; + (void)setupCoreDataStackWithStoreNamed:(NSString *)storeName; + (void)setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName; + (void)setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL; + (void)setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:(NSURL *)storeURL;
每一個調用,實例化Core Data棧的某一個部分,并提供這些實例的獲取器和設置器方法.這些實例在 MagicalRecord 中均可用,并被識別為 "默認實例".
如果工程有DEBUG標記,此時使用默認的SQLite數據存儲,不創建新的版本的數據模型而是直接改變數據模型本身的方式,將會刪除舊的存儲并自動創建一個新的.這會節省大量的時間 - 不再需要在改變數據模型后每次都重新卸載和安裝應用! 請確保發布應用時,不開啟DEBUG標記: 不告知用戶,直接刪除應用的數據,真的很不好!
在你的應用退出前,你應該調用類方法+cleanUp:
[MagicalRecord cleanUp];
這用于使用MagicalRecord后的整理工作:解除我們自定義的錯誤處理器并把MagicalRecord創建的所有的Core Data 棧設為 nil.
開啟iCloud 持久化存儲
為了更好地使用蘋果的iCloud Core Data 同步機制,使用下面初始化方法中的 一種 來替換來替換前面列出的標準初始化化方法:
+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID localStoreNamed:(NSString *)localStore; + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreNamed:(NSString *)localStoreName cloudStorePathComponent:(NSString *)pathSubcomponent; + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreNamed:(NSString *)localStoreName cloudStorePathComponent:(NSString *)pathSubcomponent completion:(void (^)(void))completion; + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID localStoreAtURL:(NSURL *)storeURL; + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreAtURL:(NSURL *)storeURL cloudStorePathComponent:(NSString *)pathSubcomponent; + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreAtURL:(NSURL *)storeURL cloudStorePathComponent:(NSString *)pathSubcomponent completion:(void (^)(void))completion;
更多細節,請閱讀 Apple's "iCloud Programming Guide for Core Data" .
注意
如果你正在管理多重是用iCloud的數據存儲,我們建議你使用那些更長的初始化方法,以自定義 contentNameKey .較短的初始化方法,會基于你應用的 bundle id(CFBundleIdentifier),自動生成 NSPersistentStoreUbiquitousContentNameKey .
操作被管理的對象上下文
對象上下文環境是你操作Core Data內數據的基礎,只有正確獲取到了上下文環境,才有可能進行相關的讀寫操作.換句話說,程序的任意位置,只要能正確獲取上下文,都能進行Core Data的操作.這也是使用Core Data共享數據的基礎之一.相較于傳統的方式,各個頁面之間只需要與一個透明的上下文環境進行交互,即可進行頁面間數據的共享.
下面是一個簡單的例子,具體含義下文都會提到:
// 獲取上下文環境 NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context]; // 在當前上下文環境中創建一個新的 Person 對象. Person *person = [Person MR_createEntityInContext:localContext]; person.firstname = firstname; person.lastname = lastname; person.age = age; // 保存修改到當前上下文中. [localContext MR_saveToPersistentStoreAndWait];
創建新的對象上下文
許多簡單的類方法可以用來幫助你創建一個新的對象上下文:
- + [NSManagedObjectContext MR_context]: 設置默認的上下文為它的父級上下文.并發類型為 NSPrivateQueueConcurrencyType .
- + [NSManagedObjectContext MR_newMainQueueContext]: 并發類型為 ** NSMainQueueConcurrencyType**.
- + [NSManagedObjectContext MR_newPrivateQueueContext]: 并發類型為 NSPrivateQueueConcurrencyType .
- + [NSManagedObjectContext MR_contextWithParent:…]: 允許自定義父級上下文.并發類型為 NSPrivateQueueConcurrencyType .
- + [NSManagedObjectContext MR_contextWithStoreCoordinator:…]:允許自定義持久化存儲協調器.并發類型為 NSPrivateQueueConcurrencyType .
默認上下文
當使用Core Data時,你經常使用的連兩類主要對象是:NSManagedObject和NSManagedObjectContext.
MagicalRecord 提供了一個簡單類方法來獲取一個默認的NSManagedObjectContext對象,這個對象在整個應用全局可用.這個上下文對象,在主線程操作,對于簡單的單線程應用來說非常強大.
為了獲取默認上下文,調用:
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
這個上下文對象,在MagicalRecord的任何需要使用上下文對象方法中都可以使用,但是并不需要給這些方法顯示提供一個指定對象管理上下文對象參數.
如果你想創建一個新的對象管理上下文對象,以用于非主線程,可使用下面的方法:
NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_context];
這將會創建一個新的對象管理上下文,和默認的上下文對象有相同的對象模型和持久化存儲;但是在另一個線程中使用時,是線程安全的.它自動設置默認上下文對象為父級上下文.
如果你想要將你的myNewContext實例作為所有獲取請求默認的上下文對象,使用下面的類方法:
[NSManagedObjectContext MR_setDefaultContext:myNewContext];
注意: 強烈 建議默認的上下文對象在主線程使用并發類型為NSMainQueueConcurrencyType的對象管理上線文對象創建和設置.
在后臺線程中執行任務
MagicalRecord 提供方法來設置和在后臺線程中使用上下文對象.后臺保存操作受UIView的動畫回調方法啟發,僅有的小小差別:
- 用于更改實體的block將永遠不會在主線程執行.
- 在你的block內部提供一個單一的 NSManagedObjectContext 上下文對象.
例如,如果我們有一個Person實體對象,并且我們需要設置它的firstName和lastName字段,下面的代碼展示了如何使用MagicalRecord來設置一個后臺保存的上下文對象:
// 獲取上下文環境 NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext]; // 在當前上下文環境中創建一個新的 Person 對象. Person *person = [Person MR_createEntityInContext:defaultContext]; person.firstname = @"firstname"; person.lastname = @"lastname"; person.age = @100; // 保存修改到當前上下文中. [defaultContext MR_saveToPersistentStoreAndWait]; [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){ Person *localPerson = [person MR_inContext:localContext]; localPerson.firstname = @"Yan"; localPerson.lastname = @"Feng"; }];
在這個方法中,指定的block給你提供了一個合適的上下文對象來執行你的操作,你不需要擔心這個上下文對象的初始化來告訴默認上線文它準備好了,并且應當更新,因為變更是在另一個線程執行.
為了在保存block完成時執行某個操作,你可以使用 completion block:
// 獲取上下文環境 NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext]; // 在當前上下文環境中創建一個新的 Person 對象. Person *person = [Person MR_createEntityInContext:defaultContext]; person.firstname = @"firstname"; person.lastname = @"lastname"; person.age = @100; // 保存修改到當前上下文中. [defaultContext MR_saveToPersistentStoreAndWait]; [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){ Person *localPerson = [person MR_inContext:localContext]; localPerson.firstname = @"Yan"; localPerson.lastname = @"Feng"; } completion:^(BOOL success, NSError *error) { NSArray * persons = [Person MR_findAll]; [persons enumerateObjectsUsingBlock:^(Person * obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"firstname: %@, lastname: %@\n", obj.firstname, obj.lastname); }]; }];
這個完成的block,在主線程(隊列)中調用,所以可以在此block里安全觸發UI更新.
創建實體對象
為了創建并插入一個新的實體實例到默認上下文對象中,你可以使用:
Person *myPerson = [Person MR_createEntity];
創建實體實例,并插入到指定的上下文中:
Person *myPerson = [Person MR_createEntityInContext:otherContext];
刪除實體對象
刪除默認上下文中的實體對象:
[myPerson MR_deleteEntity];
刪除指定上下文中的實體對象:
[myPerson MR_deleteEntityInContext:otherContext];
刪除默認上下文中的所有實體:
[Person MR_truncateAll];
刪除指定上下文中的所有實體:
[Person MR_truncateAllInContext:otherContext];
獲取實體對象
基礎查找
MagicalRecord中的大多數方法返回NSArray結果.
舉個例子,如果你有一個名為 Person 的實體,和實體 Department 關聯,你可以從持久化存儲中獲取所有的 Person 實體:
NSArray *people = [Person MR_findAll];
可以指定以某個屬性排序:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName" ascending:YES];
可以使用多個屬性進行排序:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName" ascending:YES];
當使用多個屬性進行排序時,可以單獨指定升序或降序.
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName" ascending:YES]; // 或者 NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES" ascending:NO];
如果你有辦法通過某種方式從數據庫中獲取唯一的一個對象(比如,給對象一個特定的唯一標記),你可以使用下面方法獲取某個實體對象:
Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"Forrest"];
高級查找
如果查找條件很復雜,你可以使用正則表達式:
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]];
NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];
返回 一個 NSFetchRequest
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];
NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];
每執行一次,就創建一個這些查詢條件對應的NSFetchRequest和NSSortDescriptor.
自定義查詢請求
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments]; NSFetchRequest *peopleRequest = [Person MR_requestAllWithPredicate:peopleFilter]; [peopleRequest setReturnsDistinctResults:NO]; [peopleRequest setReturnPropertiesNamed:@[@"FirstName", @"LastName"]]; NSArray *people = [Person MR_executeFetchRequest:peopleRequest];
獲取實體數量
你可以獲取持久化存儲中指定種類實體的總數量:
NSNumber *count = [Person MR_numberOfEntities];
或者,你也可以或者符合指定過濾條件的實體的總數量:
NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];
有對應的返回NSUInteger的方法:
+ (NSUInteger) MR_countOfEntities; + (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context; + (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter; + (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter inContext:(NSManagedObjectContext *)context;
集合操作
NSNumber *totalCalories = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:" onAttribute:@"calories" withPredicate:predicate]; NSNumber *mostCalories = [CTFoodDiaryEntry MR_aggregateOperation:@"max:" onAttribute:@"calories" withPredicate:predicate]; NSArray *caloriesByMonth = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:" onAttribute:@"calories" withPredicate:predicate groupBy:@"month"];
在指定上下文中查找實體
所有的查找,獲取和請求方法,都有一個對應的含有inContext:參數的方法,來讓你指定要進行某種操作的具體上下文環境:
NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext]; Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName" withValue:@"Gump" inContext:someOtherContext]; NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];
保存實體對象
何時應該保存?
通常,你的應用應該在數據變化時,將其保存到持久化存儲層中.有些應用選擇僅在應用結束時保存,但是在大多數情況下并不需要這樣做 - 實際上, 如果你僅在應用退出時保存數據,很有可能會丟失數據 !如果你的應用閃退了,會生什么?用戶會丟失所有已經保存的數據 - 這是一種非常糟糕的用戶體驗,卻又很容易避免.
如果你發現保存操作耗費了很長時間,你應該考慮使用一些方式優化:
-
在后臺線程保存: MagicalRecord 提供了一種簡捷的API來改變并立即在后臺線程保存數據 - 例如:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) { // Do your work to be saved here, against the `localContext` instance // Everything you do in this block will occur on a background thread } completion:^(BOOL success, NSError *error) { [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }];
-
把任務分割成小塊的保存任務: 某些數據量較大的任務,如導入大量的數據,應該被分割成更小塊的保存任務.沒有統一的標準規定單次保存多少任務最合適,所以你需要使用工具來測試你的應用工的性能以針對自己的應用進行調整.工具可選使用 Apple的 Instruments.
處理需要長時間運行的保存任務
當iOS應用退出時,有一個較短的時間來整理和保存數據到磁盤.如果你確定某個保存操作很可能會花費一定時間,最好的方式是請求延長應用的生命周期,比如這樣:
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) { // 這里有任何保存操作 } completion:^(BOOL success, NSError *error) {
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
請確定已經仔細 閱讀beginBackgroundTaskWithExpirationHandler相關文檔 ,因為不適當或不必要地延長應用的生命周期,可能會導致應用被App Store拒絕.
導入數據
我們在持續更新這篇文檔-謝謝您的耐心閱讀.
暫時, 推薦閱讀 Importing Data Made Easy ,它發表在 Cocoa Is My Girlfriend .這篇文檔的大部分都是基于Saul的原始文章.
MagicalRecord 團隊
MagicalRecord 支持從標準的 NSObject 實例對象,如NSArray 和 NSDictionary 直接導入進 Core Data 存儲.
使用MagicalRecord從外部數據源導入數據,需要兩步:
- 定義要導入的數據與Core Data存儲之間的映射 使用數據模型(可以少寫許多代碼!) (it's pretty much codeless!)
- 執行數據導入操作
定義導入
外部數據源的數據,在質量和結構上,可能是很混亂的,所以我們需要盡可能使MagicalRecord的導入過程更靈活.
MagicalRecord 可以從符合鍵值編碼(KVC)的對象中導入數據. 我們經常見到人們導入NSArray和NSDictionary`實例的對象,但是對于所有符合鍵值編碼(KVC)的對象都是支持的.
MagicalRecord 使用 Xcode的數據模型工具(點擊工程中TestModel.xcdatamodeld即可出現)的" User Info "的值來配置導入選項與可能的映射關系,而不用寫任何代碼.(下圖中的 mappedKeyName為系統保留字段,用來指定要映射的key,具體細節往下閱讀即可)
供參考: 用戶的模型信息中的鍵和值在一個字典中存儲,每個實體,屬性,和關系都關聯這樣一個字典.這個字典可以通過NSEntityDescription對象的userInfo方法取出.
Xcode的數據模型工具使你可以通過 Data Model Inspecto的"User Info"分組來存取這個字典.當編輯一個數據模型時,你可以使用Xcode菜單打開這個inspector - View > Utilities > Show Data Model Inspector , 或者使用快捷鍵??3.
默認地, MagicalRecord 會自動嘗試使用要導入的數據中的鍵匹配屬性和關系名. 如果一個CoreData模型中的屬性或關系名與要導入的數據中的某個鍵匹配,那你不需要做任何事 - 鍵對應的值會自動導入.
例如,如果一個實體有一個屬性名為firstName, MagicalRecord 會假定要導入的數據中也有一個名為firstName的鍵 - 如果確實存在,你的實體的firstName屬性會被設置為你要導入的數據中的firstName鍵對應的值.
往往,要導入的數據中的鍵和結構和你的實體屬性與關系不匹配.在這種情況下,你需要告訴 MagicalRecord 如何映射你要導入的數據的鍵到你的CoreData模型中匹配的屬性或關系.
我們在Core Data中接觸的三類最重要的對象-實體,屬性和屬性,都有需要在用戶info鍵組配置的選項:
屬性
鍵 | 類型 | 目的 |
---|---|---|
attributeValueClassName | String | 待定 |
dateFormat | String | 待定. 默認yyyy-MM-dd'T'HH:mm:ssz. |
mappedKeyName | String | 指定對應的要導入的數據中的keypath.支持keypath,以.分割,如location.latitude. |
mappedKeyName.[0-9] | String | 指定備用的keypath,在 mappedKeyName 指定的keypath不存在時使用.規則同上. |
useDefaultValueWhenNotPresent | Boolean | 為true時,如果要導入的數據沒有對應的鍵,就使用此屬性預設的默認值. |
實體
鍵 | 值 | 目的 |
---|---|---|
relatedByAttribute | String | 指定用來鏈接兩個實體的關系的目標實體中的屬性. |
關系
鍵 | 值 | 目的 |
---|---|---|
mappedKeyName | String | 指定對應的要導入的數據中的keypath.支持keypath,以.分割,如location.latitude. |
mappedKeyName.[0-9] | String | 指定備用的keypath,在 mappedKeyName 指定的keypath不存在時使用.規則同上. |
relatedByAttribute | String | 指定用來鏈接兩個實體的關系的目標實體中的屬性. |
type | String | 待定 |
導入對象
使用MagicalRecord導入數據到持久化存儲前,你需要知道: 你要導入的數據格式,以及如何導入.
MagicalRecord的導入數據的方法最基礎的方法是: 你知道數據應該要導入的實體,然后你可以寫一行簡單的代碼來標記數據要導入的實體.有許多方式來自定義導入的過程.
從對象自動創建一個實體實例,你可以使用更簡潔的方式:
NSDictionary *contactInfo = // JSON解析器或其他數據源返回的結果. Person *importedPerson = [Person MR_importFromObject:contactInfo];
你也可以把它分為兩步:
NSDictionary *contactInfo = // JSON解析器或其他數據源返回的結果. Person *person = [Person MR_createEntity]; // 這里不是必須為一個新創建的實體. [person MR_importValuesForKeysWithObject:contactInfo];
分為兩步的寫法,在你嘗試使用新的屬性更新已有實體時,會很有用.
+MR_importFromObject:會嘗試基于配置的查詢值(參見_relatedByAttribute_ 和 attributeNameID )來尋找一個已經存在的實體.它遵循Cocoa內置的導入相關的編程范例需要的鍵值對,和導入數據的安全方式.
+MR_importFromObject:類方法封裝了前面的使用-MR_importValuesForKeysWithObject:實例方法創建新對象的邏輯,并且會返回一個用給定數據填充的新創建的對象.
必須要注意的是,這兩種方式都是同步的.當有些導入操作會比較耗費時間時,后臺執行 所有的導入操作 仍然是非常明智的,以免對用戶交互產生不良影響.前面已經討論過, MagicalRecord 提供了便利的API來讓使用后臺線程更加易于控制:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext { Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext]; }];
導入數組
由一個JSON數組提供的一組數據或者正在導入大量的單一類型數據的情況,很常見.導入這樣的一組數據的具體實現細節,由+MR_importFromArray:類方法中能找到.
NSArray *arrayOfPeopleData = /// result from JSON parser NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];
這個方法,和+MR_importFromObject:一樣,也是同步的,所以應該使用前面提到的后臺執行block的方式來導入數據.
最佳實踐
如果你要導入的數據,可能有少許差異甚至錯亂,那就請繼續往下讀.下面列出MagicalRecord的其他的一些特性來幫助你處理一些常見的情況.
在導入時處理不良數據
API經常返回格式或或值不一致的數據.最好的方式是在你的實體對象上,使用導入的類目方法來處理.有三個方法可用:
方法 | 目的 |
---|---|
- (BOOL) shouldImport; | 在數據導入前調用.返回NO,可以終止某條特定數據的導入. |
- (void) willImport:(id)data; | 數據導入前調用. |
- (void) didImport:(id)data; | 數據導入后調用. |
通常,如果你數據是損壞的,你可能需要在導入數據前嘗試修復它.
一個常見的情況是,要導入的 JSON數據中,數字字符串很容易被誤處理為一個真實的數字.如果你想要確保某個值是以字符串形式導入,你可以這樣做:
@interface MyGreatEntity @property(readwrite, nonatomic, copy) NSString *identifier; @end @implementation MyGreatEntity @dynamic identifier; - (void)didImport:(id)data { if (NO == [data isKindOfClass:[NSDictionary class]]) { return; } NSDictionary *dataDictionary = (NSDictionary *)data; id identifierValue = dataDictionary[@"my_identifier"]; if ([identifierValue isKindOfClass:[NSNumber class]]) { NSNumber *numberValue = (NSNumber *)identifierValue; self.identifier = [numberValue stringValue]; } } @end
在導入更新時,刪除本地記錄.
有時,你可能想要在導入數據時,不僅更新數據,還要刪除本地記錄中不存在于遠程數據庫中的數據.為了實現這個效果,根據relatedByAttribute(下面的例子中是id)獲取本地所有不在更新中的實體, 并在導入新的數據前直接移除它們.
NSArray *arrayOfPeopleData = /// result from JSON parser NSArray *people = [Person MR_importFromArray:arrayOfPeopleData]; NSArray *idList = [arrayOfPeopleData valueForKey:@"id"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@)", idList]; [Person MR_deleteAllMatchingPredicate:predicate];
如果你還想在更新時在移除所有已移除的記錄的相關對象,你可以使用與上面相似的邏輯,只是要在Person的willImport:方法中實現.
@implementation Person -(void)willImport:(id)data { NSArray *idList = [data[@"posts"] valueForKey:@"id"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@) AND person.id == %@", idList, self.id]; [Post MR_deleteAllMatchingPredicate:predicate]; }
來源: http://stackoverflow.com/a/24252825/401092
日志
MagicalRecord 內部記錄大多數自身與Core Data 的交互.當在獲取或保存數據發生錯誤時,這些錯誤會被捕捉并(如果你啟用了日志)輸出到控制臺.
默認地,在debug構建時,輸出調試信息 ( MagicalRecordLoggingLevelDebug ),在release構建時,輸出錯誤信息.
日志可以通過調用[MagicalRecord setLoggingLevel:];來配置.預置的幾個錯誤日志級別:
- MagicalRecordLogLevelOff : 不輸出任何信息.
- MagicalRecordLoggingLevelError : 輸出錯誤信息.
- MagicalRecordLoggingLevelWarn : 輸出警告和錯誤
- MagicalRecordLoggingLevelInfo : 輸出信息,警告和錯誤.
- MagicalRecordLoggingLevelDebug : 輸出所有的調試信息,信息,警告和錯誤.
- MagicalRecordLoggingLevelVerbose :輸出冗長的診斷信息,信息, 警告和錯誤
日志級別,默認是MagicalRecordLoggingLevelWarn.
CocoaLumberjack
如果CocoaLumberjack可用, MagicalRecord會自動把日志交由 CocoaLumberjack .所有你需要做的就是保證CocoaLumberjack 在 MagicalRecord之前導入,像這樣:
// Objective-C #import <CocoaLumberjack/CocoaLumberjack.h> #import <MagicalRecord/MagicalRecord.h>
完全禁用日志輸出.
對大多數人來說,都很沒必要.把日志級別設為 MagicalRecordLogLevelOff 將不會有日志被打印.
及時你把日志級別設為MagicalRecordLogLevelOff, 一個快速檢查的邏輯也是會別執行當有調用日志輸出時.如果你想絕對禁用日志,你需要在編譯MagicalRecord時定義以下宏:
#define MR_LOGGING_DISABLED 1
注意,在僅在你把MagicalRecord的源文件添加到你自己的工程中時可用.你也可以把-DMR_LOGGING_DISABLED=1添加到你工程的OTHER_CFLAGS來得到相同的作用.
其他資源
下面的文章進一步討論了MagicalRecord的安裝和使用的各個方面細節(可能需要FQ):
- How to make Programming with Core Data Pleasant
- Super Happy Easy Fetching in Core Data
- Core Data and Threads, without the Headache
- Unit Testing with Core Data
注: 文章由我們 iOS122( http://www.ios122.com)的小伙伴 @顏風整理,喜歡就一起參與: iOS122 任務池