MagicalRecord詳細(xì)介紹

MagicalRecord,一個簡化CoreData操作的工具庫


簡介


在軟件工程中,活動記錄模式是一種用于在關(guān)系數(shù)據(jù)庫中存儲數(shù)據(jù)的設(shè)計(jì)模式.這種設(shè)計(jì)模式最早由Martin Fowler在他的Patterns of Enterprise Application Architecture 一書中命名.這樣的一個對象的,接口應(yīng)該包含插入,更新和刪除的方法;再加上與底層數(shù)據(jù)庫幾乎直接對應(yīng)的的屬性.

活動記錄是一種訪問數(shù)據(jù)庫中數(shù)據(jù)的方式.一個數(shù)據(jù)庫的表或者試圖被裝箱進(jìn)一個類中;因此,一個對象實(shí)例對應(yīng)表中的一行數(shù)據(jù).在創(chuàng)建對象之后,會往表中添加新的一行以保存數(shù)據(jù).加載對象時,從數(shù)據(jù)庫中獲取信息;當(dāng)對象更新時,表中對應(yīng)的行也會被更新.裝箱類實(shí)現(xiàn)存取方法和分別對應(yīng)表或視圖中每一列的屬性.

MagicalRecord 受Ruby on Rails活動記錄獲取方式的便利性影響.項(xiàng)目目標(biāo)是:

  • 清理我的Core Data相關(guān)代碼
  • 支持清晰,簡單,一行代碼式的查詢
  • 當(dāng)需要優(yōu)化請求時,仍然可以修改 NSFetchRequest.

使用 CocoaPods 安裝


  • 把下面一行添加到 Podfile :

    pod "MagicalRecord"
    
  • 在工程目錄執(zhí)行: pod update (國內(nèi)推薦使用 pod update --verbose --no-repo-update )

  • 現(xiàn)在你可以添加#import <MagicalRecord/MagicalRecord.h> 到任意項(xiàng)目源文件中,并開始使用MagicalRecord!

定義我們的數(shù)據(jù)模型


以定義實(shí)體 "Person"為例,它有屬性age, firstname和lastname.

  • 創(chuàng)建一個新的數(shù)據(jù)模型,命名為TestModel(File --> New --> File-->Core Data > Data Model)
  • 添加一個新的實(shí)體,名為Person(Add Entity)
  • 添加屬性age (Integer16), firstname (String)和 lastname (String) 4.創(chuàng)建 NSManagedObject (Editor > Create NSManagedObject Subclass… > Create)子類以更好地管理我們的實(shí)體

Core Data的初始化與清理


如果在創(chuàng)建工程之初勾選了使用Core Data的選項(xiàng),系統(tǒng)會自動在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** 類的調(diào)用:

+ (void)setupCoreDataStack;
+ (void)setupAutoMigratingCoreDataStack;
+ (void)setupCoreDataStackWithInMemoryStore;
+ (void)setupCoreDataStackWithStoreNamed:(NSString *)storeName;
+ (void)setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;
+ (void)setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL;
+ (void)setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:(NSURL *)storeURL;

每一個調(diào)用,實(shí)例化Core Data棧的某一個部分,并提供這些實(shí)例的獲取器和設(shè)置器方法.這些實(shí)例在 MagicalRecord 中均可用,并被識別為 "默認(rèn)實(shí)例".如果工程有DEBUG 標(biāo)記,此時使用默認(rèn)的SQLite數(shù)據(jù)存儲,不創(chuàng)建新的版本的數(shù)據(jù)模型而是直接改變數(shù)據(jù)模型本身的方式,將會刪除舊的存儲并自動創(chuàng)建一個新的.這會節(jié)省大量的時間 - 不再需要在改變數(shù)據(jù)模型后每次都重新卸載和安裝應(yīng)用! 請確保發(fā)布應(yīng)用時,不開啟 DEBUG 標(biāo)記: 不告知用戶,直接刪除應(yīng)用的數(shù)據(jù),真的很不好!
在你的應(yīng)用退出前,你應(yīng)該調(diào)用類方法 +cleanUp :

 [MagicalRecord cleanUp];

這用于使用MagicalRecord后的整理工作:解除我們自定義的錯誤處理器并把MagicalRecord創(chuàng)建的所有的Core Data 棧設(shè)為 nil.

開啟iCloud 持久化存儲


為了更好地使用蘋果的iCloud Core Data 同步機(jī)制,使用下面初始化方法中的一種來替換來替換前面列出的標(biāo)準(zhǔn)初始化化方法:

+ (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;
注意

如果你正在管理多重是用iCloud的數(shù)據(jù)存儲,我們建議你使用那些更長的初始化方法,以自定義contentNameKey.較短的初始化方法,會基于你應(yīng)用的 bundle id(CFBundleIdentifier),自動生成 NSPersistentStoreUbiquitousContentNameKey.

操作被管理的對象上下文


對象上下文環(huán)境是你操作Core Data內(nèi)數(shù)據(jù)的基礎(chǔ),只有正確獲取到了上下文環(huán)境,才有可能進(jìn)行相關(guān)的讀寫操作.換句話說,程序的任意位置,只要能正確獲取上下文,都能進(jìn)行Core Data的操作.這也是使用Core Data共享數(shù)據(jù)的基礎(chǔ)之一.相較于傳統(tǒng)的方式,各個頁面之間只需要與一個透明的上下文環(huán)境進(jìn)行交互,即可進(jìn)行頁面間數(shù)據(jù)的共享.

下面是一個簡單的例子,具體含義下文都會提到:

// 獲取上下文環(huán)境 
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context]; 

// 在當(dāng)前上下文環(huán)境中創(chuàng)建一個新的 Person 對象. 
Person *person = [Person MR_createEntityInContext:localContext]; person.firstname = firstname; 
person.lastname = lastname; 
person.age = age; 

// 保存修改到當(dāng)前上下文中. 
[localContext MR_saveToPersistentStoreAndWait];
創(chuàng)建新的對象上下文

許多簡單的類方法可以用來幫助你創(chuàng)建一個新的對象上下文:

+ [NSManagedObjectContext MR_context] : 設(shè)置默認(rèn)的上下文為它的父級上下文.并發(fā)類型為**NSPrivateQueueConcurrencyType**.
+ [NSManagedObjectContext MR_newMainQueueContext]: 并發(fā)類型為 ** NSMainQueueConcurrencyType**.
+ [NSManagedObjectContext MR_newPrivateQueueContext] : 并發(fā)類型為 **NSPrivateQueueConcurrencyType**.
+ [NSManagedObjectContext MR_contextWithParent:…] : 允許自定義父級上下文.并發(fā)類型為**NSPrivateQueueConcurrencyType**.
+ [NSManagedObjectContext MR_contextWithStoreCoordinator:…] :允許自定義持久化存儲協(xié)調(diào)器.并發(fā)類型為**NSPrivateQueueConcurrencyType**.
默認(rèn)上下文

當(dāng)使用Core Data時,你經(jīng)常使用的連兩類主要對象是: NSManagedObject 和 NSManagedObjectContext .
MagicalRecord 提供了一個簡單類方法來獲取一個默認(rèn)的 NSManagedObjectContext
對象,這個對象在整個應(yīng)用全局可用.這個上下文對象,在主線程操作,對于簡單的單線程應(yīng)用來說非常強(qiáng)大.

為了獲取默認(rèn)上下文,調(diào)用:

NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

這個上下文對象,在MagicalRecord的任何需要使用上下文對象方法中都可以使用,但是并不需要給這些方法顯示提供一個指定對象管理上下文對象參數(shù).

如果你想創(chuàng)建一個新的對象管理上下文對象,以用于非主線程,可使用下面的方法:

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_context];

這將會創(chuàng)建一個新的對象管理上下文,和默認(rèn)的上下文對象有相同的對象模型和持久化存儲;但是在另一個線程中使用時,是線程安全的.它自動設(shè)置默認(rèn)上下文對象為父級上下文.

如果你想要將你的 myNewContext 實(shí)例作為所有獲取請求默認(rèn)的上下文對象,使用下面的類方法:

注意: 強(qiáng)烈 建議默認(rèn)的上下文對象在主線程使用并發(fā)類型為NSMainQueueConcurrencyType
的對象管理上線文對象創(chuàng)建和設(shè)置.

在后臺線程中執(zhí)行任務(wù)

MagicalRecord 提供方法來設(shè)置和在后臺線程中使用上下文對象.后臺保存操作受UIView的動畫回調(diào)方法啟發(fā),僅有的小小差別:

  • 用于更改實(shí)體的block將永遠(yuǎn)不會在主線程執(zhí)行.
  • 在你的block內(nèi)部提供一個單一的 NSManagedObjectContext 上下文對象.

例如,如果我們有一個Person實(shí)體對象,并且我們需要設(shè)置它的firstName和lastName字段,下面的代碼展示了如何使用MagicalRecord來設(shè)置一個后臺保存的上下文對象:

// 獲取上下文環(huán)境
  NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
  // 在當(dāng)前上下文環(huán)境中創(chuàng)建一個新的 Person 對象.
  Person *person = [Person MR_createEntityInContext:defaultContext];
  person.firstname = @"firstname";
  person.lastname = @"lastname";
  person.age      = @100;

  // 保存修改到當(dāng)前上下文中.
  [defaultContext MR_saveToPersistentStoreAndWait];
  [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

  Person *localPerson = [person MR_inContext:localContext];
  localPerson.firstname = @"Yan";
  localPerson.lastname = @"Feng";
}];

在這個方法中,指定的block給你提供了一個合適的上下文對象來執(zhí)行你的操作,你不需要擔(dān)心這個上下文對象的初始化來告訴默認(rèn)上線文它準(zhǔn)備好了,并且應(yīng)當(dāng)更新,因?yàn)樽兏窃诹硪粋€線程執(zhí)行.
為了在保存block完成時執(zhí)行某個操作,你可以使用 completion block:

// 獲取上下文環(huán)境
  NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

  // 在當(dāng)前上下文環(huán)境中創(chuàng)建一個新的 Person 對象.
  Person *person = [Person MR_createEntityInContext:defaultContext];
  person.firstname = @"firstname";
  person.lastname = @"lastname";
  person.age      = @100;

  // 保存修改到當(dāng)前上下文中.
  [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,在主線程(隊(duì)列)中調(diào)用,所以可以在此block里安全觸發(fā)UI更新.

創(chuàng)建實(shí)體對象


為了創(chuàng)建并插入一個新的實(shí)體實(shí)例到默認(rèn)上下文對象中,你可以使用:

Person *myPerson = [Person MR_createEntity];

創(chuàng)建實(shí)體實(shí)例,并插入到指定的上下文中:

Person *myPerson = [Person MR_createEntityInContext:otherContext];

刪除實(shí)體對象


刪除默認(rèn)上下文中的實(shí)體對象:

[myPerson MR_deleteEntity];

刪除指定上下文中的實(shí)體對象:

[myPerson MR_deleteEntityInContext:otherContext];

刪除默認(rèn)上下文中的所有實(shí)體:

[Person MR_truncateAll];

刪除指定上下文中的所有實(shí)體:

[Person MR_truncateAllInContext:otherContext];

獲取實(shí)體對象


基礎(chǔ)查找

MagicalRecord中的大多數(shù)方法返回 NSArray 結(jié)果.
舉個例子,如果你有一個名為 Person 的實(shí)體,和實(shí)體 Department 關(guān)聯(lián),你可以從持久化存儲中獲取所有的 Person 實(shí)體:

NSArray *people = [Person MR_findAll];

可以指定以某個屬性排序:

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName" ascending:YES];

可以使用多個屬性進(jìn)行排序:

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName" ascending:YES];

當(dāng)使用多個屬性進(jìn)行排序時,可以單獨(dú)指定升序或降序.

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName" ascending:YES];
// 或者
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES" ascending:NO];

如果你有辦法通過某種方式從數(shù)據(jù)庫中獲取唯一的一個對象(比如,給對象一個特定的唯一標(biāo)記),你可以使用下面方法獲取某個實(shí)體對象:

Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"Forrest"];
高級查找

如果查找條件很復(fù)雜,你可以使用正則表達(dá)式:

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];

每執(zhí)行一次,就創(chuàng)建一個這些查詢條件對應(yīng)的 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];
獲取實(shí)體數(shù)量

你可以獲取持久化存儲中指定種類實(shí)體的總數(shù)量:

NSNumber *count = [Person MR_numberOfEntities];

或者,你也可以或者符合指定過濾條件的實(shí)體的總數(shù)量:

NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];

有對應(yīng)的返回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"];
在指定上下文中查找實(shí)體

所有的查找,獲取和請求方法,都有一個對應(yīng)的含有 inContext: 參數(shù)的方法,來讓你指定要進(jìn)行某種操作的具體上下文環(huán)境:

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];
Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName" withValue:@"Gump" inContext:someOtherContext];
NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

保存實(shí)體對象


何時應(yīng)該保存?

通常,你的應(yīng)用應(yīng)該在數(shù)據(jù)變化時,將其保存到持久化存儲層中.有些應(yīng)用選擇僅在應(yīng)用結(jié)束時保存,但是在大多數(shù)情況下并不需要這樣做 - 實(shí)際上,如果你僅在應(yīng)用退出時保存數(shù)據(jù),很有可能會丟失數(shù)據(jù)!如果你的應(yīng)用閃退了,會生什么?用戶會丟失所有已經(jīng)保存的數(shù)據(jù) - 這是一種非常糟糕的用戶體驗(yàn),卻又很容易避免.

如果你發(fā)現(xiàn)保存操作耗費(fèi)了很長時間,你應(yīng)該考慮使用一些方式優(yōu)化:
1.在后臺線程保存: MagicalRecord 提供了一種簡捷的API來改變并立即在后臺線程保存數(shù)據(jù) - 例如:

[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;
}];

2.把任務(wù)分割成小塊的保存任務(wù): 某些數(shù)據(jù)量較大的任務(wù),如導(dǎo)入大量的數(shù)據(jù),應(yīng)該被分割成更小塊的保存任務(wù).沒有統(tǒng)一的標(biāo)準(zhǔn)規(guī)定單次保存多少任務(wù)最合適,所以你需要使用工具來測試你的應(yīng)用工的性能以針對自己的應(yīng)用進(jìn)行調(diào)整.工具可選使用 Apple的 Instruments.

處理需要長時間運(yùn)行的保存任務(wù)

當(dāng)iOS應(yīng)用退出時,有一個較短的時間來整理和保存數(shù)據(jù)到磁盤.如果你確定某個保存操作很可能會花費(fèi)一定時間,最好的方式是請求延長應(yīng)用的生命周期,比如這樣:

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;
}];

導(dǎo)入數(shù)據(jù)


MagicalRecord 支持從標(biāo)準(zhǔn)的 NSObject 實(shí)例對象,如NSArray 和 NSDictionary 直接導(dǎo)入進(jìn) Core Data 存儲.
使用MagicalRecord從外部數(shù)據(jù)源導(dǎo)入數(shù)據(jù),需要兩步:

  • 定義要導(dǎo)入的數(shù)據(jù)與Core Data存儲之間的映射 使用數(shù)據(jù)模型(可以少寫許多代碼!) (it's pretty much codeless!)
  • 執(zhí)行數(shù)據(jù)導(dǎo)入操作
定義導(dǎo)入

外部數(shù)據(jù)源的數(shù)據(jù),在質(zhì)量和結(jié)構(gòu)上,可能是很混亂的,所以我們需要盡可能使MagicalRecord的導(dǎo)入過程更靈活.
MagicalRecord 可以從符合鍵值編碼(KVC)的對象中導(dǎo)入數(shù)據(jù). 我們經(jīng)常見到人們導(dǎo)入NSArray 和 NSDictionary`實(shí)例的對象,但是對于所有符合鍵值編碼(KVC)的對象都是支持的.
MagicalRecord 使用 Xcode的數(shù)據(jù)模型工具(點(diǎn)擊工程中TestModel.xcdatamodeld即可出現(xiàn))的"User Info"的值來配置導(dǎo)入選項(xiàng)與可能的映射關(guān)系,而不用寫任何代碼.(下圖中的 mappedKeyName為系統(tǒng)保留字段,用來指定要映射的key,具體細(xì)節(jié)往下閱讀即可)

供參考: 用戶的模型信息中的鍵和值在一個字典中存儲,每個實(shí)體,屬性,和關(guān)系都關(guān)聯(lián)這樣一個字典.這個字典可以通過NSEntityDescription對象的userInfo 方法取出.

Xcode的數(shù)據(jù)模型工具使你可以通過 Data Model Inspecto的"User Info"分組來存取這個字典.當(dāng)編輯一個數(shù)據(jù)模型時,你可以使用Xcode菜單打開這個inspector - View > Utilities > Show Data Model Inspector, 或者使用快捷鍵 ??3.

默認(rèn)地, MagicalRecord 會自動嘗試使用要導(dǎo)入的數(shù)據(jù)中的鍵匹配屬性和關(guān)系名. 如果一個CoreData模型中的屬性或關(guān)系名與要導(dǎo)入的數(shù)據(jù)中的某個鍵匹配,那你不需要做任何事 - 鍵對應(yīng)的值會自動導(dǎo)入.

例如,如果一個實(shí)體有一個屬性名為 firstName , MagicalRecord 會假定要導(dǎo)入的數(shù)據(jù)中也有一個名為 firstName 的鍵 - 如果確實(shí)存在,你的實(shí)體的 firstName 屬性會被設(shè)置為你要導(dǎo)入的數(shù)據(jù)中的 firstName 鍵對應(yīng)的值.

往往,要導(dǎo)入的數(shù)據(jù)中的鍵和結(jié)構(gòu)和你的實(shí)體屬性與關(guān)系不匹配.在這種情況下,你需要告訴 MagicalRecord 如何映射你要導(dǎo)入的數(shù)據(jù)的鍵到你的CoreData模型中匹配的屬性或關(guān)系.

我們在Core Data中接觸的三類最重要的對象-實(shí)體,屬性和屬性,都有需要在用戶info鍵組配置的選項(xiàng):

屬性

| 鍵 |類型| 目的 |
|: ------|: -------|: ------------------------------------|
|attributeValueClassName| String | 待定|
|dateFormat|String|待定. 默認(rèn) yyyy-MM-dd'T'HH:mm:ssz.|
|mappedKeyName |String|指定對應(yīng)的要導(dǎo)入的數(shù)據(jù)中的keypath.支持keypath,以 . 分割,如location.latitude .|
|mappedKeyName.[0-9] |String |指定備用的keypath,在mappedKeyName指定的keypath不存在時使用.規(guī)則同上. |
|useDefaultValueWhenNotPresent|Boolean | 為true時,如果要導(dǎo)入的數(shù)據(jù)沒有對應(yīng)的鍵,就使用此屬性預(yù)設(shè)的默認(rèn)值.|

實(shí)體

| 鍵 |值|目的 |
|: ------|:-------:|: ------------------------------------|
|relatedByAttribute|String | 指定用來鏈接兩個實(shí)體的關(guān)系的目標(biāo)實(shí)體中的屬性.|

關(guān)系

| 鍵 |值| 目的 |
|: ------|:-------:|: ------------------------------------|
|mappedKeyName|String | 指定對應(yīng)的要導(dǎo)入的數(shù)據(jù)中的keypath.支持keypath,以 . 分割,如location.latitude.|
|****mappedKeyName.[0-9]****|String | 指定備用的keypath,在mappedKeyName指定的keypath不存在時使用.規(guī)則同上.|
|****relatedByAttribute****|String | 指定用來鏈接兩個實(shí)體的關(guān)系的目標(biāo)實(shí)體中的屬性.|
|type|String | 待定|

導(dǎo)入對象

使用MagicalRecord導(dǎo)入數(shù)據(jù)到持久化存儲前,你需要知道: 你要導(dǎo)入的數(shù)據(jù)格式,以及如何導(dǎo)入.

MagicalRecord的導(dǎo)入數(shù)據(jù)的方法最基礎(chǔ)的方法是: 你知道數(shù)據(jù)應(yīng)該要導(dǎo)入的實(shí)體,然后你可以寫一行簡單的代碼來標(biāo)記數(shù)據(jù)要導(dǎo)入的實(shí)體.有許多方式來自定義導(dǎo)入的過程.

從對象自動創(chuàng)建一個實(shí)體實(shí)例,你可以使用更簡潔的方式:

NSDictionary *contactInfo = // JSON解析器或其他數(shù)據(jù)源返回的結(jié)果.
Person *importedPerson = [Person MR_importFromObject:contactInfo];

你也可以把它分為兩步:

NSDictionary *contactInfo = // JSON解析器或其他數(shù)據(jù)源返回的結(jié)果.
Person *person = [Person MR_createEntity]; // 這里不是必須為一個新創(chuàng)建的實(shí)體.
[person MR_importValuesForKeysWithObject:contactInfo];

分為兩步的寫法,在你嘗試使用新的屬性更新已有實(shí)體時,會很有用.

+MR_importFromObject: 會嘗試基于配置的查詢值(參見relatedByAttributeattributeNameID)來尋找一個已經(jīng)存在的實(shí)體.它遵循Cocoa內(nèi)置的導(dǎo)入相關(guān)的編程范例需要的鍵值對,和導(dǎo)入數(shù)據(jù)的安全方式.

+MR_importFromObject: 類方法封裝了前面的使用-MR_importValuesForKeysWithObject: 實(shí)例方法創(chuàng)建新對象的邏輯,并且會返回一個用給定數(shù)據(jù)填充的新創(chuàng)建的對象.

必須要注意的是,這兩種方式都是同步的.當(dāng)有些導(dǎo)入操作會比較耗費(fèi)時間時,后臺執(zhí)行 所有的導(dǎo)入操作仍然是非常明智的,以免對用戶交互產(chǎn)生不良影響.前面已經(jīng)討論過, MagicalRecord 提供了便利的API來讓使用后臺線程更加易于控制:

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext { 
    Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext];
}];
導(dǎo)入數(shù)組

由一個JSON數(shù)組提供的一組數(shù)據(jù)或者正在導(dǎo)入大量的單一類型數(shù)據(jù)的情況,很常見.導(dǎo)入這樣的一組數(shù)據(jù)的具體實(shí)現(xiàn)細(xì)節(jié),由+MR_importFromArray: 類方法中能找到.

NSArray *arrayOfPeopleData = /// result from JSON parser
NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

這個方法,和 +MR_importFromObject: 一樣,也是同步的,所以應(yīng)該使用前面提到的后臺執(zhí)行block的方式來導(dǎo)入數(shù)據(jù).

最佳實(shí)踐

如果你要導(dǎo)入的數(shù)據(jù),可能有少許差異甚至錯亂,那就請繼續(xù)往下讀.下面列出MagicalRecord的其他的一些特性來幫助你處理一些常見的情況.

在導(dǎo)入時處理不良數(shù)據(jù)

API經(jīng)常返回格式或或值不一致的數(shù)據(jù).最好的方式是在你的實(shí)體對象上,使用導(dǎo)入的類目方法來處理.有三個方法可用:

| 方法 | 目的 |
|: ------|: ----------|
|- (BOOL) shouldImport;|在數(shù)據(jù)導(dǎo)入前調(diào)用.返回 NO ,可以終止某條特定數(shù)據(jù)的導(dǎo)入.|
|- (void) willImport:(id)data;|數(shù)據(jù)導(dǎo)入前調(diào)用.|
|- (void) didImport:(id)data;|數(shù)據(jù)導(dǎo)入后調(diào)用.|
通常,如果你數(shù)據(jù)是損壞的,你可能需要在導(dǎo)入數(shù)據(jù)前嘗試修復(fù)它.

一個常見的情況是,要導(dǎo)入的 JSON數(shù)據(jù)中,數(shù)字字符串很容易被誤處理為一個真實(shí)的數(shù)字.如果你想要確保某個值是以字符串形式導(dǎo)入,你可以這樣做:

@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

在導(dǎo)入更新時,刪除本地記錄.
有時,你可能想要在導(dǎo)入數(shù)據(jù)時,不僅更新數(shù)據(jù),還要刪除本地記錄中不存在于遠(yuǎn)程數(shù)據(jù)庫中的數(shù)據(jù).為了實(shí)現(xiàn)這個效果,根據(jù)relatedByAttribute (下面的例子中是 id )獲取本地所有不在更新中的實(shí)體, 并在導(dǎo)入新的數(shù)據(jù)前直接移除它們.

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];

如果你還想在更新時在移除所有已移除的記錄的相關(guān)對象,你可以使用與上面相似的邏輯,只是要在Person 的 willImport: 方法中實(shí)現(xiàn).

@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];
}

日志


MagicalRecord 內(nèi)部記錄大多數(shù)自身與Core Data 的交互.當(dāng)在獲取或保存數(shù)據(jù)發(fā)生錯誤時,這些錯誤會被捕捉并(如果你啟用了日志)輸出到控制臺.
默認(rèn)地,在debug構(gòu)建時,輸出調(diào)試信息 (MagicalRecordLoggingLevelDebug),在release構(gòu)建時,輸出錯誤信息.
日志可以通過調(diào)用[MagicalRecord setLoggingLevel:];來配置.預(yù)置的幾個錯誤日志級別:

  • MagicalRecordLogLevelOff: 不輸出任何信息.
  • MagicalRecordLoggingLevelError: 輸出錯誤信息.
  • MagicalRecordLoggingLevelWarn: 輸出警告和錯誤
  • MagicalRecordLoggingLevelInfo: 輸出信息,警告和錯誤.
  • MagicalRecordLoggingLevelDebug: 輸出所有的調(diào)試信息,信息,警告和錯誤.
  • MagicalRecordLoggingLevelVerbose:輸出冗長的診斷信息,信息, 警告和錯誤

日志級別,默認(rèn)是 MagicalRecordLoggingLevelWarn

CocoaLumberjack

如果CocoaLumberjack可用, MagicalRecord會自動把日志交由 CocoaLumberjack.所有你需要做的就是保證CocoaLumberjack 在 MagicalRecord之前導(dǎo)入,像這樣:

// Objective-C
#import <CocoaLumberjack/CocoaLumberjack.h>
#import <MagicalRecord/MagicalRecord.h>

完全禁用日志輸出.

對大多數(shù)人來說,都很沒必要.把日志級別設(shè)為MagicalRecordLogLevelOff將不會有日志被打印.

及時你把日志級別設(shè)為 MagicalRecordLogLevelOff , 一個快速檢查的邏輯也是會別執(zhí)行當(dāng)有調(diào)用日志輸出時.如果你想絕對禁用日志,你需要在編譯MagicalRecord時定義以下宏:

#define MR_LOGGING_DISABLED 1

注意,在僅在你把MagicalRecord的源文件添加到你自己的工程中時可用.你也可以把-DMR_LOGGING_DISABLED=1 添加到你工程的OTHER_CFLAGS 來得到相同的作用.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容