1 前言
CoreData不僅僅是數(shù)據(jù)庫(kù)舰讹,而是蘋果封裝的一個(gè)更高級(jí)的數(shù)據(jù)持久化框架施籍,SQLite只是其提供的一種數(shù)據(jù)存儲(chǔ)方法。CoreData對(duì)數(shù)據(jù)的查找做了很大的優(yōu)化领舰,提供了大量的API返帕。CoreData的遺憾之一是不能設(shè)置唯一主鍵桐玻,但是它可以設(shè)置實(shí)體的UserInfo(relatedByAttribute)來(lái)實(shí)現(xiàn)主鍵的功能,另外UserInfo字典也擴(kuò)展了CoreData的功能溉旋,可以通過(guò)代碼取得這個(gè)字典進(jìn)行自定義操作畸冲。但是通常并不會(huì)完全自己建立CoreData,常用的第三方框架是MagicRecord,在其GitHub主要上有設(shè)置唯一主鍵及其他詳盡的使用方法邑闲。
2 重要的類
2.1 NSManagedObjectModel
NSManagedObjectModel可以認(rèn)為是對(duì)整個(gè)數(shù)據(jù)庫(kù)中各個(gè)表的各個(gè)字段和表之間聯(lián)系的描述算行,它不僅包含每個(gè)模型對(duì)象的屬性,還包含該模型對(duì)象和其他模型對(duì)象之間的關(guān)系即relationship苫耸。
2.2 NSPersistentStore
NSPersistentStore代表真正存儲(chǔ)的數(shù)據(jù)州邢,CoreData提供了SQLite等四種存儲(chǔ)模式。除SQLite外另外三種模式在對(duì)數(shù)據(jù)庫(kù)操作時(shí)需要將所有的數(shù)據(jù)全部讀入褪子。同時(shí)SQLite是默認(rèn)的存儲(chǔ)方式量淌。除了四種默認(rèn)的存儲(chǔ)模式外,CoreData還允許開(kāi)發(fā)者通過(guò)創(chuàng)建NSIncrementalStore來(lái)自定義存儲(chǔ)格式嫌褪。
2.3 NSPersistentStoreCoordinator
NSPersistentStoreCoordinator是模型文件描述NSManagedObjectModel和數(shù)據(jù)存儲(chǔ)NSPersistentStore之間的橋梁呀枢。前者只關(guān)心整個(gè)數(shù)據(jù)庫(kù)各個(gè)表結(jié)構(gòu)及表之間的聯(lián)系,是一個(gè)抽象的概念笼痛。后者只關(guān)心數(shù)據(jù)的實(shí)際存儲(chǔ)而并不關(guān)心數(shù)據(jù)對(duì)應(yīng)的對(duì)象裙秋。Coordinator作為橋梁將數(shù)據(jù)庫(kù)文件轉(zhuǎn)化為具體的對(duì)象。
2.4 NSManagedObjectContext
NSManagedObjectContext是操作上下文缨伊,也是我們的工作區(qū)摘刑,通常APP中會(huì)有一個(gè)版本的模型文件描述NSManagedObjectModel,一個(gè)對(duì)應(yīng)的數(shù)據(jù)存儲(chǔ)文件NSPersistentStore及一個(gè)它們之間的橋梁NSPersistentStoreCoordinator刻坊。和多個(gè)工作區(qū)NSManagedObjectContext枷恕。大多數(shù)時(shí)候只需要維護(hù)一個(gè)工作區(qū)即可,對(duì)數(shù)據(jù)庫(kù)的所有編輯操作都將被保存在這個(gè)工作區(qū)中谭胚,只有當(dāng)其執(zhí)行完save操作時(shí)徐块,數(shù)據(jù)庫(kù)才會(huì)被更改。多工作區(qū)的情況見(jiàn)CoreData多上下文操作灾而。另外NSManagedObjectContext需要注意以下幾點(diǎn)蛹锰。
- 1)NSManagedObjectContext管理它創(chuàng)建或者抓取的對(duì)象的生命周期。
- 2)一個(gè)對(duì)象必須依靠一個(gè)Context存在绰疤,每個(gè)對(duì)象都會(huì)引用管理它的Context可以通過(guò)
object.managedObjectContext
獲取,這是一個(gè)weak弱引用舞终。 - 3)一個(gè)對(duì)象在整個(gè)生命周期內(nèi)都只會(huì)對(duì)一個(gè)Context保持引用轻庆。
- 4)一個(gè)應(yīng)用程序可以擁有多個(gè)Context。
- 5)Context是線程不安全的敛劝,對(duì)每個(gè)的對(duì)象余爆,創(chuàng)建、修改和刪除操作必須在同一個(gè)線程中完成夸盟。
2.5 NSPersistentStoreDescription
NSPersistentStoreDescription是在iOS10后新增的蛾方,其主要用于為NSPersistentContainer配置數(shù)據(jù)遷移和數(shù)據(jù)存儲(chǔ)的URL等信息。
2.6 NSPersistentContainer
在iOS10后,CoreDataStack概念被引入桩砰,NSPersistentContainer也是在iOS10過(guò)后新增的拓春,它可以有效的將前四個(gè)類的對(duì)象結(jié)合起來(lái),在程序中只用通過(guò)指定name創(chuàng)建一個(gè)NSPersistentContainer的實(shí)例亚隅,然后為它配置NSPersistentStore硼莽。CoreData會(huì)自動(dòng)創(chuàng)建其他相關(guān)的實(shí)例進(jìn)行數(shù)據(jù)庫(kù)初始化。初始化完成后的數(shù)據(jù)庫(kù)URL和主工作區(qū)都可以通過(guò)其persistentStoreDescription屬性中的URL方法拿到煮纵。另外也可以手動(dòng)指定其persistentStoreDescription屬性配置數(shù)據(jù)遷移和數(shù)據(jù)存儲(chǔ)URL等信息懂鸵。
3 建立CoreData
3.1 初始化CoreData
CoreData的初始化工作需要在AppDelegate的applicationDidFinishLaunching中以同步方式在主線程中進(jìn)行,因?yàn)槿绻麛?shù)據(jù)庫(kù)無(wú)法正確初始化行疏,整個(gè)程序的運(yùn)行都將無(wú)意義匆光。
在iOS項(xiàng)目中使用CoreData持久化存儲(chǔ)數(shù)據(jù)可以在創(chuàng)建項(xiàng)目時(shí)勾選CoreData選項(xiàng),此時(shí)系統(tǒng)會(huì)在Appdelegate中自動(dòng)生成NSPersistentContainer及其相關(guān)代碼酿联,此外工程中也將多一個(gè)以項(xiàng)目名稱命名的.xcdatamodeld文件终息。但是通常不會(huì)使用系統(tǒng)自帶的這個(gè)功能而是手動(dòng)建立CoreData,并且通常使用MagicRecord第三方框架货葬。
iOS10中蘋果引入了CoreDataStack堆棧的概念采幌,因此下面分別介紹iOS10之前、iOS10之后震桶、使用MagicRecord初始化CoreData的方法休傍。
iOS10之前
lazy var managerContext: NSManagedObjectContext = {
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
let model = NSManagedObjectModel(contentsOfURL: NSBundle.mainBundle().URLForResource("Person", withExtension: "momd")!)!
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last! + "/person.db"
let url = NSURL(fileURLWithPath: path)
try! coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
context.persistentStoreCoordinator = coordinator
return context
}()
static let sharedCoreDataManager = HYFCoreDataManager()
iOS10之后
var storeURL : URL {
let storePaths = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)
let storePath = storePaths[0] as NSString
let fileManager = FileManager.default
do {
try fileManager.createDirectory(
atPath: storePath as String,
withIntermediateDirectories: true,
attributes: nil)
} catch {
print("Error creating storePath \(storePath): \(error)")
}
let sqliteFilePath = storePath
.appendingPathComponent(storeName + ".sqlite")
return URL(fileURLWithPath: sqliteFilePath)
}
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
//指定Descriptions可以指定數(shù)據(jù)庫(kù)的存儲(chǔ)位置,但是一般不設(shè)置蹲姐,由CoreData設(shè)置默認(rèn)值磨取,
//默認(rèn)設(shè)置為支持?jǐn)?shù)據(jù)庫(kù)遷移,支持自動(dòng)推斷映射模型柴墩,默認(rèn)SQLite存儲(chǔ)忙厌,其默認(rèn)的URL可以通過(guò)
//Container的persistentStore屬性中的URL方法拿到。
container.persistentStoreDescriptions = [self.storeDescription]
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
print("Unersolved error \(error), \(error.userInfo)")
}
}
return container;
}()
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
lazy var storeDescription: NSPersistentStoreDescription = {
let description = NSPersistentStoreDescription(url: self.storeURL)
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
//description.type = NSInMemoryStoreType
return description
}()
MagicRecord中
//默認(rèn)為支持?jǐn)?shù)據(jù)遷移支持自動(dòng)推斷映射模型江咳,其數(shù)據(jù)存儲(chǔ)URL可以通過(guò)MagicRecord類方法獲得
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"Database.sqlite"];
3.2 新建模型文件
在工程中新建一個(gè)CoreData分類下的DataModel文件逢净,現(xiàn)在只考慮單個(gè)版本的模型文件,多版本管理和數(shù)據(jù)遷移在后續(xù)文章中介紹歼指。首先看到的是三個(gè)部分爹土,左側(cè)列出了所有的實(shí)體,中間列出了某個(gè)實(shí)體的所有屬性以及和其他實(shí)體之間的關(guān)系踩身。右側(cè)為通過(guò)工具面板胀茵,當(dāng)選中左側(cè)的某個(gè)實(shí)體或者中間的某個(gè)實(shí)體的某個(gè)屬性來(lái)進(jìn)行更詳盡的編輯。
3.2.1 新建實(shí)體
實(shí)體可以理解為對(duì)某個(gè)對(duì)象的描述挟阻,它在SQLite數(shù)據(jù)庫(kù)中具體體現(xiàn)為一張表琼娘。選中實(shí)體時(shí)右側(cè)通用工具欄中最后一個(gè)Data Model Inspector可以進(jìn)行給更多細(xì)節(jié)的編輯峭弟。
Entity描述中,這里的Abstract Entity表示抽象實(shí)體脱拼,意味著不會(huì)創(chuàng)建具體的實(shí)體瞒瘸,通常一個(gè)抽象實(shí)體是多個(gè)具體實(shí)體的父實(shí)體,如抽象實(shí)體Attachment可以對(duì)應(yīng)幾個(gè)具體的子實(shí)體ImageAttachment和VideoAttachment挪拟。
Class描述中挨务,通常Codegen選中Manual/None,表示由開(kāi)發(fā)者手動(dòng)建立實(shí)體類的OC文件玉组,否則由CoreData自動(dòng)生成谎柄。手動(dòng)建立具體通過(guò)選中某個(gè)模型文件,在XCode菜單的Editor中選則創(chuàng)建Create NSManagedObject Subclass...惯雳。此時(shí)工程中會(huì)為某個(gè)實(shí)例生成兩個(gè)文件朝巫,分別為【實(shí)例名+CoreDataProperties.swift】和【實(shí)例名+CoreDataClass.swift】,如果需要生成OC文件需在選中模型文件后在右側(cè)的通用工具欄的第一項(xiàng)菜單下將Code Generation改為Objective-C石景。兩個(gè)文件作用在后續(xù)創(chuàng)建NSManagedObject類中介紹劈猿。此時(shí)Class區(qū)域內(nèi)Name會(huì)被自動(dòng)填充為實(shí)體名,Swift中涉及到命名空間問(wèn)題潮孽,需要將其Module設(shè)置為當(dāng)前Module揪荣。
UserInfo描述,它可以自定用用戶信息往史,擴(kuò)展CoreData功能仗颈,在MagicRecord中通過(guò)relatedByAttribute設(shè)置唯一主鍵。
Versioning描述椎例,這里應(yīng)該是關(guān)于實(shí)體版本控制的挨决,但在數(shù)據(jù)遷移中主要判斷的是模型的版本而不是實(shí)體的版本,暫時(shí)未用到订歪,具體用法還行參考官網(wǎng)描述脖祈。
3.2.2 新建屬性
Attribute Type
新建屬性時(shí),CoreData支持的類型和在代碼中映射的類型對(duì)應(yīng)關(guān)系為刷晋,【Integer16 - NSNumber】盖高、【Integer32 - NSNumber】、【Integer64 - NSNumber】眼虱、【Decimal - NSDecimalNumber】或舞、【Double - NSNumber】、【Float - NSNumber】蒙幻、【String - String】、【Boolean - Bool】胆筒、【Date - NSDate】邮破、【Binary Data - NSData】诈豌、【Transformable - NSObject】。
Interger:在指定整形的Attribute type時(shí)需根據(jù)樣本的實(shí)際情況選擇合適類型抒和。
- Integer 16 有符號(hào)的占2字節(jié)整數(shù) -32768~32767
- Integer 32 有符號(hào)的占4字節(jié)整數(shù) -2147483648 ~ 2147483647
- Integer 64 有符號(hào)的占8字節(jié)整數(shù) ...
Decimal:它表示一種科學(xué)計(jì)數(shù)法矫渔,具體為10^exponent,exponent is an integer from –128 through 127摧莽。
NSDecimalNumber *number = [NSDecimalNumber decimalNumberWithMantissa:1234 exponent:-2 isNegative:NO]; //12.34
number = [NSDecimalNumber decimalNumberWithMantissa:1234 exponent:2 isNegative:YES]; //-123400
Binary Data:其映射類型為NSData用于存儲(chǔ)類似于圖片庙洼、PDF文件或者其他任何能被編碼為二進(jìn)制的資源。這里不需要擔(dān)心從內(nèi)存中加載大量二進(jìn)制數(shù)據(jù)問(wèn)題镊辕,因?yàn)镃ore Data已經(jīng)對(duì)這個(gè)問(wèn)題作出了優(yōu)化油够。在一個(gè)實(shí)體中,該類型的屬性右方的Attribute設(shè)置面板中勾選Allows External Storage選項(xiàng)征懈,這時(shí)Core Data會(huì)自動(dòng)對(duì)每一個(gè)實(shí)體對(duì)象檢查判斷是否將改資源作為二進(jìn)制數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)中石咬,或者單獨(dú)將其存儲(chǔ)在主存中并為其創(chuàng)建一個(gè)指向該資源的通用標(biāo)識(shí)符URI,并在數(shù)據(jù)庫(kù)中存儲(chǔ)URI卖哎。至于什么時(shí)候會(huì)存儲(chǔ)URI鬼悠,在官方文檔中暫時(shí)未找到合理解釋,在論壇中有見(jiàn)提到是根據(jù)要存儲(chǔ)的數(shù)據(jù)大小來(lái)決定的亏娜,但是在很多案例中焕窝,這依然是個(gè)可行而高效的方法。在存儲(chǔ)時(shí)需手動(dòng)轉(zhuǎn)換為NSData進(jìn)行存儲(chǔ)维贺,讀取時(shí)也應(yīng)手動(dòng)從NSData轉(zhuǎn)換為相應(yīng)的變量類型它掂。
這里需要注意的是,大多數(shù)開(kāi)發(fā)者都指出了當(dāng)使用Allows External Storage這種方式儲(chǔ)存資源在做數(shù)據(jù)庫(kù)遷移時(shí)有一個(gè)隱含bug幸缕,誘因是數(shù)據(jù)庫(kù)遷移后新的統(tǒng)一存儲(chǔ)該資源文件的文件夾會(huì)被刪除并重新創(chuàng)建群发,從而導(dǎo)致數(shù)據(jù)丟失,以下是一種解決方案发乔。
- (NSPersistentStoreCoordinator*)persistentStoreCoordinator {
if (_persistentStoreCoordinator !=nil) {
return _persistentStoreCoordinator;
}
NSURL*storeURL =[[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataBinaryBug.sqlite"];
NSError*error =nil;
NSDictionary*sourceMetadata =[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeURL
error:&error];
//Check if the new model is compatible with any previously stored model
BOOL isCompatibile = [self.managedObjectModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
BOOL needsMigration =!isCompatibile;
NSFileManager*fileManager =[NSFileManager defaultManager];
//Prepare a temporary path to move CoreData's external data storage folder to if automatic model migration is required
NSString*documentsPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString*tmpPathToExternalStorage =[documentsPath stringByAppendingPathComponent:@"tmpPathToReplacementData"];
NSString*pathToExternalStorage =[documentsPath stringByAppendingPathComponent:@".CoreDataBinaryBug_SUPPORT/_EXTERNAL_DATA"];
if (needsMigration) {
if ([fileManager fileExistsAtPath:pathToExternalStorage]) {
//Move Apple's CoreData external storage folder before it's nuked by the migration bug
[fileManager moveItemAtPath:pathToExternalStorage toPath:tmpPathToExternalStorage error:nil];
}
}
NSDictionary*options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption,[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator =[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
} else {
if (needsMigration) {
//Apple's automatic migration is now complete. Replace the default external storage folder with the version pre upgrade
[[NSFileManager defaultManager] removeItemAtPath:pathToExternalStorage error:nil];
[[NSFileManager defaultManager] moveItemAtPath:tmpPathToExternalStorage toPath:pathToExternalStorage error:nil];
}
}
return _persistentStoreCoordinator;
}
Transformable:只要遵守NSCoding協(xié)議的對(duì)象都能被以Transformable類型的方式存儲(chǔ)熟妓,默認(rèn)的映射類型為NSObject,可以直接將從數(shù)據(jù)庫(kù)中獲得的對(duì)象進(jìn)行強(qiáng)制類型轉(zhuǎn)變?yōu)楫?dāng)前類栏尚。同時(shí)這類型實(shí)體還允許在Data Model Inspector中通過(guò)Value Transfo...關(guān)聯(lián)一個(gè)類并實(shí)現(xiàn)以下操作輔助將該對(duì)象轉(zhuǎn)化為另外一個(gè)符合NSCoding協(xié)議對(duì)象來(lái)存儲(chǔ)起愈。當(dāng)再模型文件中直接引用工程中的文件時(shí),必須指定Module译仗,通常是項(xiàng)目名稱抬虽。NSCoding用法。
class ImageTransformer: ValueTransformer {
override class func transformedValueClass() -> AnyClass {
return NSData.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }
return UIImage(data: data)
}
override func transformedValue(_ value: Any?) -> Any? {
guard let image = value as? UIImage else { return nil }
return UIImagePNGRepresentation(image)
}
}
小結(jié):在選擇屬性類型時(shí)纵菌,通常字符串阐污、數(shù)字、Bool類型數(shù)據(jù)都有直接與之對(duì)應(yīng)的類型咱圆,但是對(duì)于UIImage笛辟,UIcolor以及自定義類等數(shù)據(jù)類型并沒(méi)有直接與之匹配的屬性類型功氨,此時(shí)可以將某個(gè)類型分開(kāi)存儲(chǔ),使用時(shí)再將其合成手幢,如UIColor可以分解為RGBA四個(gè)部分整數(shù)分開(kāi)存儲(chǔ)捷凄,但更為有效的是在Binary Data和Transformable類型中選擇合適的類型。
Attribute通用工具欄設(shè)置
選中一個(gè)屬性時(shí)围来,右側(cè)也會(huì)出現(xiàn)三個(gè)可選界面跺涤,分別是File Inspector、Quick Help Inspector和Data Model Inspector监透,在Data Model Inspector中也可以對(duì)屬性進(jìn)行高級(jí)設(shè)置桶错,不同類型屬性的高級(jí)設(shè)置面板有部分變動(dòng),以下以Integer 32類型為例才漆。
Attribute描述:其中Properties中Optional表示該屬性是否為必有屬性牛曹,對(duì)應(yīng)Swift中的必選屬性,當(dāng)指定為必選屬性時(shí)需為其指定默認(rèn)值醇滥,兩位兩個(gè)屬性暫未用過(guò)黎比。Validation表示對(duì)數(shù)據(jù)的校驗(yàn),這里可以設(shè)置數(shù)據(jù)校驗(yàn)規(guī)則鸳玩,它負(fù)責(zé)對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)阅虫,不在此范圍內(nèi)的數(shù)據(jù)不會(huì)被存儲(chǔ)到Core Data中,這個(gè)錯(cuò)誤將在調(diào)用Context的Save方法時(shí)候拋出不跟。如果后期版本迭代時(shí)此處發(fā)生改變颓帝,需要進(jìn)行輕量級(jí)數(shù)據(jù)遷移。同時(shí)這里可以設(shè)置最大最小和默認(rèn)值窝革。Advanced中兩個(gè)勾選框暫未用過(guò)购城。
User Info描述:這里添加的字點(diǎn)再代碼中都可以拿到,用于擴(kuò)展CoreData的功能虐译,在MagicRecord中瘪板,可以添加mappedKeyName-Value來(lái)將后來(lái)返回的Value字段轉(zhuǎn)換為Attribute本身。
Versioning描述漆诽,同樣這里應(yīng)該是關(guān)于屬性版本控制的侮攀,但在數(shù)據(jù)遷移中主要判斷的是模型的版本而不是屬性的版本,暫時(shí)未用到厢拭,具體用法還行參考官網(wǎng)描述兰英。
3.2.3 新建關(guān)系
在CoreData中,表之間的聯(lián)系需要設(shè)置為關(guān)系供鸠,比如一個(gè)公司實(shí)體Company擁有很多雇員實(shí)體Employee畦贸。這里Company和Employee在數(shù)據(jù)庫(kù)中分別為兩張表,而實(shí)現(xiàn)上述需求需要在Company實(shí)體中添加一個(gè)Destination為Employee的employees關(guān)系楞捂。
在CoreData中關(guān)系分為三類薄坏,一對(duì)一正林,一對(duì)多和多對(duì)多。需要特別注意的是無(wú)論哪種類型的關(guān)系颤殴,關(guān)系都是成對(duì)出現(xiàn)的,并且必須設(shè)置Inverse鼻忠。并且關(guān)系可以指向?qū)嶓w自身涵但。
一對(duì)一的關(guān)系只需要在實(shí)體A中添加指向?qū)嶓wB的to-one類型關(guān)系,并且在食堂B中同樣添加指向A的to-one類型關(guān)系帖蔓,同時(shí)將兩個(gè)關(guān)系互相設(shè)置為inverse矮瘟。一對(duì)多的關(guān)系需要在實(shí)體A中添加指向?qū)嶓wB的to-many類型關(guān)系,并且在食堂B中同樣添加指向A的to-one類型關(guān)系塑娇,同時(shí)將兩個(gè)關(guān)系互相設(shè)置為inverse澈侠。多對(duì)多的關(guān)系需要在實(shí)體A中添加指向?qū)嶓wB的to-many類型關(guān)系,并且在食堂B中同樣添加指向A的to-many類型關(guān)系埋酬,同時(shí)將兩個(gè)關(guān)系互相設(shè)置為inverse哨啃。
關(guān)系高級(jí)設(shè)置選項(xiàng)中,properties通常保留默認(rèn)值為optional写妥,type根據(jù)需要選擇拳球,Delete Rule通常選擇為Nullify,其余選項(xiàng)下面介紹珍特。Count可以設(shè)置最大最小的數(shù)量祝峻,Advanced暫未用過(guò),保留默認(rèn)值即可扎筒。User Info和Versioning描述同前文類似莱找。Arrangement表示是否排序,當(dāng)選擇to-Many類型關(guān)系時(shí)嗜桌,關(guān)系中的元素將以集合Set形式組織數(shù)據(jù)而非數(shù)組奥溺。勾選ordered會(huì)使用有序集合NSInorderedSet,此時(shí)生成的【實(shí)例名+CoreDataProperties.swift】文件中CoreData會(huì)自動(dòng)生成集合的操作方法症脂。在MagicRecord中可以向UserInfo中添加relatedByAttribute字段指定排序?qū)傩浴?/p>
幾種關(guān)系的刪除規(guī)則Delete Rule:
- Nullify(作廢):當(dāng)A對(duì)象的關(guān)系指向的B對(duì)象被刪除后谚赎,A對(duì)象的關(guān)系將被設(shè)為nil。對(duì)于To Many關(guān)系類型诱篷,B對(duì)象只會(huì)從A對(duì)象的關(guān)系的容器中被移除壶唤。
- Cascade(級(jí)聯(lián)):當(dāng)B對(duì)象的關(guān)系指向的C對(duì)象被刪除后,B對(duì)象也會(huì)被刪除棕所。B對(duì)象關(guān)聯(lián)(以Cascade刪除規(guī)則)的二級(jí)對(duì)象A也會(huì)被刪除闸盔。以此類推。
- Deny(拒絕):如果刪除A對(duì)象時(shí)琳省,A對(duì)象的關(guān)系指向的B對(duì)象仍存在迎吵,則刪除操作會(huì)被拒絕躲撰。
- NO Action:當(dāng)A對(duì)象的關(guān)系指向的B對(duì)象被刪除后,A對(duì)象保持不變击费,這意味著A對(duì)象的關(guān)系會(huì)指向一個(gè)不存在的對(duì)象拢蛋。如果沒(méi)有充分的理由,最好不要使用蔫巩。
3.3 創(chuàng)建NSManagedObject實(shí)體類
選中某個(gè)模型文件NSManagedObjectModle后谆棱,在XCode的菜單欄中選擇Editor可以為模型創(chuàng)建NSManagedObject實(shí)體類,當(dāng)創(chuàng)建一個(gè)實(shí)例的類時(shí)圆仔,系統(tǒng)自行生成兩個(gè)不同文件【實(shí)例名 +CoreDataProperties.swift 】和【實(shí)例名+CoreDataClass.swift】垃瞧。其中第一個(gè)只包含所有屬性,第二個(gè)包含所有操作坪郭。這樣設(shè)計(jì)的目的是个从,當(dāng)后期為實(shí)例增加屬性時(shí)再?gòu)腅ditor選項(xiàng)中創(chuàng)建對(duì)應(yīng)類時(shí)只會(huì)重新生成CoreDataProperties.swift文件,避免對(duì)CoreDataClass.swift文件的修改歪沃。
4 操作數(shù)據(jù)庫(kù)
4.1 簡(jiǎn)單的操作
插入數(shù)據(jù)
let walk = Walk(context: managedContex)
walk.date = NSDate()
currentDog?.addToWalks(walk)
do {
try managedContex.save()
} catch let error as NSError {
print("Save error: \(error), description: \(error.userInfo)")
}
刪除數(shù)據(jù)嗦锐,只有執(zhí)行save后才會(huì)被真正刪除,注意刪除數(shù)據(jù)必須是一件謹(jǐn)慎的事情绸罗,在iOS9之前必須將程序中所有對(duì)被刪除數(shù)據(jù)的引用置為nill意推,否則會(huì)引發(fā)CoreData異常導(dǎo)致程序崩潰,幸運(yùn)的是在iOS9過(guò)后珊蟀,NSMnagedObjectContex對(duì)象有一個(gè)默認(rèn)為True的屬性shouldDeleteInaccessibleFaults菊值,當(dāng)其為True時(shí),對(duì)被刪除的數(shù)據(jù)操作將返回nil育灸。
guard let walkToRemove = currentDog?.walks?[indexPath.row] as? Walk, editingStyle == .delete else {
return
}
managedContex.delete(walkToRemove)
do {
try managedContex.save()
} catch let error as NSError {
print("Saving error: \(error), description: \(error.userInfo)")
}
查詢數(shù)據(jù)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return;
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
do {
people = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
錯(cuò)誤處理
對(duì)于一個(gè)NSError的正確處理應(yīng)該是檢查錯(cuò)誤的Domain和Error code腻窒,標(biāo)準(zhǔn)的處理流程見(jiàn)官網(wǎng)。
4.2 查詢操作
4.2.1 NSFetchRequest基本使用
創(chuàng)建一個(gè)FetchRequest的方式有5種
let fetchRequest1 = NSFetchRequest<Dog>()
let entity = NSEntityDescription.entity(forEntityName: "Dog", in: managedContex)
fetchRequest1.entity = entity
let fetchRequest2 = NSFetchRequest<Dog>(entityName: "Dog")
let fetchRequest3: NSFetchRequest<Dog> = Dog.fetchRequest()
let fetchRequest4 = managedObjectModel.fetchRequestTemplate(forName: "venueFR")
let fetchRequest5 = managedObjectModel.fetchRequestTemplate(forName: "venueFR", substitutionVariables: ["NAME" : "Vivi bubble Tea"])
其中4磅崭、5方法都需要在.xcdatamodeld文件中的添加實(shí)體按鈕下拉選項(xiàng)中選擇添加可視化的fetchRequest儿子。并通過(guò)Coordinator的managedObjectModel生成fetchRequest對(duì)象。注意其中的name參數(shù)必須嚴(yán)格與CoreData Editor中的fetchRequest名字一致砸喻,否則程序?qū)?huì)崩潰柔逼。
當(dāng)程序中對(duì)于某個(gè)對(duì)象存在大量復(fù)雜查找時(shí),并不注重排序時(shí)可以通過(guò)以上兩種方法割岛,其優(yōu)點(diǎn)是少些代碼愉适,缺點(diǎn)是不能排序。
guard let model = coreDataStack.managedContext.persistentStoreCoordinator?.managedObjectModel,
let fetchRequest = model.fetchRequestTemplate(forName: "FetchRequest") as? NSFetchRequest<Venue> else {
return
}
NSFetchRequest的resultType有四個(gè)值癣漆,其中.managedObjectResultType為默認(rèn)值维咸,返回的是滿足條件的對(duì)象,.countResultType返回的是滿足條件對(duì)象的個(gè)數(shù),.dictionaryResultType返回了一個(gè)字典癌蓖,其中包含平均值瞬哼、最大最小值等統(tǒng)計(jì)信息,.managedObjectIDResultType返回滿足條件的唯一標(biāo)識(shí)符租副。仔細(xì)選擇返回類型坐慰,在某些時(shí)候會(huì)極大提升程序運(yùn)行效率。需要注意的是用僧,當(dāng)設(shè)置了某個(gè)具體resultType時(shí)讨越,NSFetchRequest的范形需要與之對(duì)應(yīng)。.managedObjectIDResultType返回的是一個(gè)NSManagedObjectID對(duì)象數(shù)組永毅,因?yàn)檫@個(gè)屬性是線程安全的,在iOS5以前經(jīng)常使用人弓,但是在之后很少使用沼死,因?yàn)镃oreData提供了更好的處理方式。
當(dāng)查找符合某個(gè)條件的對(duì)象數(shù)量時(shí)崔赌,方法1示例如下:
let fetchRequest = NSFetchRequest<NSNumber>(entityName: "Venue")
fetchRequest.resultType = .countResultType
另外也可以不設(shè)置請(qǐng)求類型意蛀,直接調(diào)用context的方法,方法2如下:
let count = try coreDataStack.managedContext.count(for: fetchRequest)
dictionaryResultType:查找一個(gè)類所有數(shù)據(jù)某個(gè)屬性的統(tǒng)計(jì)結(jié)果用法很多健芭,關(guān)于統(tǒng)計(jì)的可選函數(shù)列表見(jiàn)NSExpression文檔县钥,下面只展示兩個(gè)實(shí)例。
案例一:求和
let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "Venue")
fetchRequest.resultType = .dictionaryResultType
let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "sumDeals"
let specialCountExp = NSExpression(forKeyPath: #keyPath(Venue.specialCount))
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [specialCountExp])
sumExpressionDesc.expressionResultType = .integer32AttributeType
fetchRequest.propertiesToFetch = [sumExpressionDesc]
do {
let results = try coreDataStack.managedContext.fetch(fetchRequest)
let resultDict = results.first!
let numDeals = resultDict["sumDeals"]!
numDealsLabel.text = "\(numDeals) total deals"
} catch let error as NSError {
print("Count not fetch \(error), \(error.userInfo)")
}
案例二:計(jì)數(shù)
func totalEmployeesPerDepartmentFast() -> [[String: String]] {
//1 創(chuàng)建NSExpressionDescription命名為“headCount”
let expressionDescreption = NSExpressionDescription()
expressionDescreption.name = "headCount"
//2 創(chuàng)建函數(shù)統(tǒng)計(jì)每個(gè)"department"的成員數(shù)量,更多的函數(shù)關(guān)鍵字如average慈迈,sum若贮,count,min等見(jiàn)NSExpression文檔
expressionDescreption.expression =
NSExpression(forFunction: "count:",
arguments: [NSExpression(forKeyPath: "department")])
//3 通過(guò)設(shè)置propertiesToFetch初始化fetch的內(nèi)容痒留,這樣CoreData就不會(huì)查尋每條記錄的所有數(shù)據(jù)谴麦,這里只查詢"department"屬性,并通過(guò)expressionDescreption函數(shù)記錄不同"department"的數(shù)量伸头。
let fetchRequest: NSFetchRequest<NSDictionary> = NSFetchRequest(entityName: "Employee")
// 這兩個(gè)參數(shù)都是必須的匾效,第一個(gè)"department"只會(huì)關(guān)注對(duì)應(yīng)的屬性并不會(huì)關(guān)注統(tǒng)計(jì),其對(duì)應(yīng)結(jié)果是【"department":name】的字典恤磷,第二個(gè)參數(shù)expressionDescreption只關(guān)注統(tǒng)計(jì)結(jié)果并不關(guān)注具體是哪一個(gè)department面哼,其結(jié)果是【"headCount":value】的字典
fetchRequest.propertiesToFetch = ["department", expressionDescreption]
//查詢結(jié)果以"department"分組,這樣將返回一個(gè)數(shù)組
fetchRequest.propertiesToGroupBy = ["department"]
fetchRequest.resultType = .dictionaryResultType
//4 執(zhí)行查詢操作
var fetchResults: [NSDictionary] = []
do {
fetchResults = try coreDataStack.mainContext.fetch(fetchRequest)
} catch let error as NSError {
print("ERROR: \(error.localizedDescription)")
return [[String: String]]()
}
//5 查詢的結(jié)果是一個(gè)[NSDictionary]扫步,其中元素個(gè)數(shù)取決于fetchRequest.propertiesToGroupBy的分組個(gè)數(shù)魔策,每個(gè)字典的元素個(gè)數(shù)取決于fetchRequest.propertiesToFetch中的個(gè)數(shù)。在上述兩個(gè)屬性都未設(shè)置時(shí)锌妻,其結(jié)果為[NSManagedObject]代乃。
return fetchResults as! [[String: String]]
}
4.2.2 NSPredicate限制NSFetchRequest
在數(shù)據(jù)庫(kù)中抓取數(shù)據(jù)的時(shí)候,CoreData會(huì)順著每一個(gè)實(shí)體的relationships去查詢相關(guān)實(shí)體,當(dāng)這種關(guān)系非常復(fù)雜搁吓,或者查詢的實(shí)體自身數(shù)量龐大的時(shí)候原茅,這會(huì)十分消耗性能。幸運(yùn)的是堕仔,CoreData可以通過(guò)以下三種方式來(lái)優(yōu)化效率擂橘,1)CoreData支持分批查找,可以設(shè)置NSFetchRequest的fetchBatchSize摩骨、fetchLimit和fetchOffset屬性進(jìn)行控制通贞。2)CoreData使用faulting來(lái)優(yōu)化內(nèi)存效率,一個(gè)fault是一個(gè)占位對(duì)象恼五,表示還沒(méi)有完全加載入內(nèi)存的一個(gè)類型昌罩。3)使用NSPredicate限制查詢范圍。
NSPredicate條件可以通過(guò)AND灾馒,OR茎用,NOT等各種條件限制,這個(gè)類是Foundation的內(nèi)容睬罗,具體使用可以查詢官網(wǎng)轨功。NSDescriptor也是屬于Foundation的內(nèi)容。根據(jù)文檔中介紹容达,這兩個(gè)屬性是在SQLite level這一層生效古涧。NSDescriptor有很多API可以得到一個(gè)comparator,NSPredicate也有很多實(shí)例化方法花盐,但是CoreData并不會(huì)全部支持羡滑,因?yàn)樵撜Z(yǔ)法在SQLite生效,部分高效的方法無(wú)法轉(zhuǎn)化為SQLite的語(yǔ)法算芯。
lazy var nameSortDescriptor: NSSortDescriptor = {
let compareSelector = #selector(NSString.localizedStandardCompare(_:))
return NSSortDescriptor(key: #keyPath(Venue.name), ascending: true, selector: compareSelector)
}()
lazy var dsitanceSortDescriptor: NSSortDescriptor = {
return NSSortDescriptor(key: #keyPath(Venue.location.distance), ascending: true)
}()
4.2.3 異步抓取
和NSFetchRequest是NSPersistentStoreRequest的子類一樣啄栓,NSAsynchronousFetchRequest也是NSPersistentStoreRequest的子類,它可以在子線程對(duì)大量數(shù)據(jù)抓取也祠。它需要一個(gè)NSFetchResult的對(duì)象進(jìn)行初始化昙楚,包含一個(gè)完成回調(diào),在managedContext中調(diào)用execute執(zhí)行查詢操作诈嘿。另外異步抓取請(qǐng)求可以通過(guò)NSAsynchronousFetchRequest對(duì)象的cancel()方法撤銷堪旧。異步抓取也可以使用Context執(zhí)行perform方法來(lái)實(shí)現(xiàn)。兩種方案任選其一即可奖亚,暫未發(fā)現(xiàn)它們之間的本質(zhì)區(qū)別淳梦。
fetchRequest = Venue.fetchRequest()
asyncFetchRequest = NSAsynchronousFetchRequest<Venue>(fetchRequest: fetchRequest, completionBlock: { [unowned self] (result: NSAsynchronousFetchResult) in
guard let venues = result.finalResult else {
return
}
self.venues = venues
self.tableView.reloadData()
})
do {
try coreDataStack.managedContext.execute(asyncFetchRequest)
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
4.3 批量操作
4.3.1 批量更新
有時(shí)可能需要批量改變數(shù)據(jù)庫(kù)中某一個(gè)實(shí)體所有對(duì)象的單個(gè)屬性,首先傳統(tǒng)的將所有符合條件的對(duì)象從數(shù)據(jù)庫(kù)中加載到內(nèi)存中能夠?qū)崿F(xiàn)昔字。但是當(dāng)需要處理成千上萬(wàn)條記錄的時(shí)候爆袍,這樣將會(huì)極大的浪費(fèi)內(nèi)存首繁,降低效率。在iOS8以后陨囊,CoreData提供了更有效的操作弦疮,NSBatchUpdateRequest可以繞過(guò)加載到內(nèi)存的操作,直接對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)進(jìn)行批量更新蜘醋。如郵件app中標(biāo)記所有為已讀胁塞。
let batchUpdate = NSBatchUpdateRequest(entityName: "Venue")
batchUpdate.propertiesToUpdate = [#keyPath(Venue.favorite) : true]
batchUpdate.affectedStores = coreDataStack.managedContext.persistentStoreCoordinator?.persistentStores
batchUpdate.resultType = .updatedObjectsCountResultType
do {
let batchResult = try coreDataStack.managedContext.execute(batchUpdate) as! NSBatchUpdateResult
print("Records updated \(batchResult.result!)")
} catch let error as NSError {
print("Could not update \(error), \(error.userInfo)")
}
4.3.1 批量刪除
同樣的NSBatchDeleteRequest也是NSPersistentStoreRequest的一個(gè)子類,它和NSBatchUpdateRequest一樣直接對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作压语,注意這兩個(gè)類的操作將不會(huì)把對(duì)象和Context進(jìn)行關(guān)聯(lián)啸罢,因此也不會(huì)對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),因此當(dāng)執(zhí)行這兩個(gè)操作時(shí)需要手動(dòng)進(jìn)行數(shù)據(jù)校驗(yàn)胎食。
4.4 NSFetchedResultsController
NSFetchedResultsController是蘋果特地為支持UITableView從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)設(shè)計(jì)的一個(gè)類扰才。NSFetchedResultsController可以通過(guò)fetchRequest、managedObjectContext厕怜、sectionNameKeyPath和cacheName四個(gè)參數(shù)實(shí)例化一個(gè)對(duì)象训桶。
其中第三個(gè)參數(shù)sectionNameKeyPath為分組的屬性字段,注意它不僅可以取一級(jí)屬性酣倾,還可以取多級(jí)屬性,如Team.qualifyZone.lon...谤专。但是需要注意的是真正想讓數(shù)據(jù)分組展示必須使用相應(yīng)的NSSortDescriptor賦值給NSFetchedResultsController進(jìn)行數(shù)據(jù)排序躁锡。只要fetchRequest的NSSortDescriptor的sortDescriptors屬性數(shù)組中的首個(gè)元素和NSFetchedResultsController初始化時(shí)的分組使用同一鍵值,那么這里的升序或者降序不會(huì)對(duì)分組結(jié)果造成影響置侍,只會(huì)影響排序映之。
第四個(gè)參數(shù)cacheName用于緩存分組相關(guān)信息,注意盡管這里并不是在數(shù)據(jù)庫(kù)中分類存儲(chǔ)蜡坊,但是當(dāng)重啟程序后分組信息依然有效杠输,這是因?yàn)槠渚唧w緩存位置在主存Disk中。另外秕衙,當(dāng)某個(gè)Request的條件改變時(shí)或者查詢另外一個(gè)實(shí)體時(shí)蠢甲,如果使用了同一個(gè)name進(jìn)行緩存,需要調(diào)用deleteCatch(withName:)或者使用另外一個(gè)不同的name据忘,因此name盡量取得更有意義鹦牛。
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest: NSFetchRequest<Team> = Team.fetchRequest()
let zoneSort = NSSortDescriptor(key: #keyPath(Team.qualifyingZone), ascending: true)
let scoreSort = NSSortDescriptor(key: #keyPath(Team.wins), ascending: false)
let nameSort = NSSortDescriptor(key: #keyPath(Team.teamName), ascending: true)
fetchRequest.sortDescriptors = [zoneSort, scoreSort, nameSort]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: coreDataStack.managedContext,
sectionNameKeyPath: #keyPath(Team.qualifyingZone),
cacheName: "worldCup")
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Fetching error: \(error), \(error.userInfo)")
}
}
獲取抓取到的數(shù)據(jù)
func numberOfSections(in tableView: UITableView) -> Int {
guard let sections = fetchedResultsController.sections else {
return 0
}
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sectionInfo = fetchedResultsController.sections?[section] else {
return 0
}
return sectionInfo.numberOfObjects
}
let team = fetchedResultsController.object(at: indexPath)
監(jiān)聽(tīng)數(shù)據(jù)改變
當(dāng)某個(gè)fetchedResultsController的context對(duì)數(shù)據(jù)進(jìn)行改變時(shí),這個(gè)信息會(huì)發(fā)到它的delegate中勇吊。這里需要注意的是曼追,當(dāng)你點(diǎn)擊了tableview的某一行,可能導(dǎo)致數(shù)據(jù)更新Update同時(shí)還導(dǎo)致了數(shù)據(jù)排序Move汉规,CoreData會(huì)將這兩個(gè)操作合并為一個(gè)操作礼殊,并只調(diào)用一次didChange 方法并且NSFetchedResultsChangeType = Move。
extension ViewController: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
case .update:
let cell = tableView.cellForRow(at: indexPath!) as! TeamCell
configure(cell: cell, for: indexPath!)
case .move:
tableView.deleteRows(at: [indexPath!], with: .automatic)
tableView.insertRows(at: [newIndexPath!], with: .automatic)
}
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
let indexSet = IndexSet(integer: sectionIndex)
switch type {
case .insert:
tableView.insertSections(indexSet, with: .automatic)
case .delete:
tableView.deleteSections(indexSet, with: .automatic)
default:
break
}
}
}
需要注意的是每次內(nèi)容改變第二和第四個(gè)方法只會(huì)調(diào)用一個(gè),當(dāng)不會(huì)新增或者刪除分區(qū)Section時(shí)晶伦,CoreData調(diào)用第一碟狞、第二和第三個(gè)方法,當(dāng)發(fā)生新增或者刪除分區(qū)Section時(shí)坝辫,CoreData調(diào)用第一篷就、第三和第四個(gè)方法。當(dāng)某個(gè)分區(qū)只有一個(gè)對(duì)象時(shí)近忙,這個(gè)對(duì)象被刪除后就會(huì)調(diào)用類型為.delete的方法四竭业。或者添加了一個(gè)包含新的分區(qū)的對(duì)象及舍,就會(huì)調(diào)用類型為.insert的方法四未辆。
同樣的,NSFetchedResultsController同樣對(duì)UICollectionview有很好的支持,不同的是,CollectionView并沒(méi)有beginUpdate和EndUpdate方法米辐,因此需要在NSFetchedResultsController代理中didchanged中進(jìn)行UI更新蚜枢。
在使用NSFetchedResultsController的代理時(shí),應(yīng)注意杠巡,只要是它管理的實(shí)體數(shù)據(jù)庫(kù)發(fā)生一點(diǎn)改變,其代理都會(huì)被調(diào)用。
5 小結(jié)
通常并不會(huì)手動(dòng)從0開(kāi)始初始化CoreData遗契,更常用的是使用MagicRecordRecord進(jìn)行數(shù)據(jù)庫(kù)初始化。但是其并不能處理復(fù)雜的數(shù)據(jù)遷移病曾,因此我們需要在調(diào)用它的初始化方法之前先手動(dòng)進(jìn)行數(shù)據(jù)庫(kù)遷移牍蜂。關(guān)于MagicRecord詳細(xì)用法見(jiàn)其Github主頁(yè).