托管對(duì)象上下文 : NSManagedObejctContext
數(shù)據(jù)庫中的數(shù)據(jù)行在剛被取出時(shí)是Array或Hash形式固阁,這種數(shù)據(jù)結(jié)構(gòu)并不直觀备燃,為了便于使用并齐,通常會(huì)進(jìn)行對(duì)象關(guān)系映射客税,即把類模擬為數(shù)據(jù)行的模板更耻,取出數(shù)據(jù)行時(shí)捏膨,將數(shù)據(jù)行轉(zhuǎn)換為該類的對(duì)象号涯。
把數(shù)據(jù)行存進(jìn)去是個(gè)相反的過程链快,向類的對(duì)象寫數(shù)據(jù)域蜗,再把這個(gè)對(duì)象轉(zhuǎn)為數(shù)據(jù)行存入數(shù)據(jù)庫。
一進(jìn)一出的過程并非憑空完成筑累,它們由 NSManagedObejctContext 控制疼阔。
比如我們來存一條數(shù)據(jù):
//Insert
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext as! NSManagedObejctContext
let student = NSEntityDescription.insertNewObject(forEntityName: "Student" , into: moc) as! Student
student.id = 1
student.name = "Alice"
student.age = 18
do {
try moc.save()
print("保存成功")
} catch {
fatalError("\(error)")
}
托管對(duì)象模型 : NSManagedObjectModel
簡(jiǎn)單地說,它用來描述數(shù)據(jù)庫的結(jié)構(gòu)巫橄、表關(guān)系茵典。
它的實(shí)例化需要.momd文件,這個(gè)文件存在模擬器沙盒中彩倚,具體位置像這樣:/Users/gaoya/Library/Developer/CoreSimulator/Devices/D07B71B5-8508-47F5-8181-6309D819F52B/data/Containers/Bundle/Application/3B9F46F7-74E1-44AB-8DCD-FBEC3C894EDC/test.app/test.momd
我猜這個(gè)文件是在安裝app的時(shí)候帆离,根據(jù)源文件中的.xcdatamodel文件生成的
實(shí)例化 NSManagedObjectModel:
let modelUrl = Bundle.main.url(forResource: "test.momd/test 2", withExtension: "mom")!
let managedObjectModel = NSManagedObjectModel.init(contentsOf: modelUrl)
持久化存儲(chǔ)協(xié)調(diào)器 : NSPersistentStoreCooedinator
負(fù)責(zé)管理底層存儲(chǔ)文件哥谷。它包括模型和真實(shí)數(shù)據(jù)。
模型參照上面的NSManagedObjectModel猜扮,真實(shí)數(shù)據(jù)的儲(chǔ)存的方式可以有很多種旅赢,sqlite鲜漩、二進(jìn)制集惋、xml刮刑、json等雷绢。無論用哪一種方式儲(chǔ)存沽讹,真實(shí)數(shù)據(jù)都需要與模型的結(jié)構(gòu)保持一致愿汰,持久化存儲(chǔ)協(xié)調(diào)器的工作就在于此。
自定義上下文的數(shù)據(jù)庫:
//獲取momd文件
let modelUrl = Bundle.main.url(forResource: "test.momd/test 2", withExtension: "mom")!
//使用momd文件實(shí)例化托管對(duì)象模型
let managedObjectModel = NSManagedObjectModel.init(contentsOf: modelUrl)
//使用托管對(duì)象模型實(shí)例化持久化存儲(chǔ)協(xié)調(diào)器
let coordinator = NSPersistentStoreCoordinator.init(managedObjectModel: managedObjectModel! )
//獲取沙盒的Document路徑
var applicationDocumentsDirectory: URL = {
let urls = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)
return urls[urls.count - 1]
}()
let url = applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
//為持久化存儲(chǔ)協(xié)調(diào)器添加真實(shí)數(shù)據(jù)
try! coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
//創(chuàng)建一個(gè)上下文
let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
//為上下文設(shè)置持久化存儲(chǔ)協(xié)調(diào)器
managedObjectContext.persistentStoreCoordinator = coordinator
獲取沙盒的Document路徑就像這樣: /Users/gaoya/Library/Developer/CoreSimulator/Devices/D07B71B5-8508-47F5-8181-6309D819F52B/data/Containers/Data/Application/57E04E49-DF06-49BE-805D-48CD22D02EB1/Documents
,sqlite文件并非一定要儲(chǔ)存在這里惹骂,只要能獲取到就行。
實(shí)體描述 : NSEntityDescription
Entity相當(dāng)于數(shù)據(jù)表轻要,NSEntityDescription 代表一張數(shù)據(jù)表椭懊。
查詢表結(jié)構(gòu):
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
//et 是 NSEntityDescription實(shí)例
for et in (moc.persistentStoreCoordinator?.managedObjectModel.entities)! {
print("表名:\(et.name!)")
print("字段:")
for (k,_) in et.attributesByName {
print(k)
}
}
抓取請(qǐng)求 : NSFetchRequest
NSFetchRequest代表將請(qǐng)求的內(nèi)容,也代表優(yōu)化過的sql語句
// Select
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Student")
request.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)] // 依 id 由小到大排序
let selectId = "1"
request.predicate = NSPredicate(format: "id = \(selectId )") //查找條件
do {
let results =
try moc.fetch(request) as! [Student]
for result in results {
print("查詢結(jié)果:")
print("\(result.id). \(result.name!)")
print("年齡: \(result.age)")
}
} catch {
fatalError("\(error)")
}
遷移計(jì)劃 : NSEntityMigrationPolicy
描述一次遷移計(jì)劃踪宠,在映射模型MappingModel文件中指定Custom Policy為自定義的繼承自NSEntityMigrationPolicy的類,Custom Policy處填寫[工程名.類名]柳琢,在該類中復(fù)寫以下可選方法绍妨,則遷移時(shí)會(huì)相應(yīng)調(diào)用它們。
當(dāng)遷移將要開始時(shí)
func beginEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool在舊數(shù)據(jù)上構(gòu)建新的實(shí)例時(shí)
func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool在舊數(shù)據(jù)上構(gòu)建新的實(shí)例結(jié)束時(shí)
func endInstanceCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool構(gòu)建新的RelationShips時(shí)
func createRelationshipsForDestinationInstance(dInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool`構(gòu)建新的RelationShips結(jié)束時(shí)
func endRelationshipCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool驗(yàn)證,保存數(shù)據(jù)時(shí)
func performCustomValidationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool遷移結(jié)束時(shí)
func endEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool
數(shù)據(jù)遷移 : NSMigrationManager
當(dāng)iOS App升級(jí)時(shí)柬脸,本地庫可能會(huì)改動(dòng)他去,如果你直接改動(dòng)本地?cái)?shù)據(jù)庫結(jié)構(gòu),編譯倒堕,安裝(覆蓋舊版本)灾测,打開。
不出意外App會(huì)崩掉涩馆,因?yàn)閿?shù)據(jù)庫結(jié)構(gòu)改動(dòng)了行施,而真實(shí)數(shù)據(jù)沒改動(dòng)允坚。
此時(shí),你應(yīng)該遷移你的數(shù)據(jù)庫蛾号,根據(jù)需求稠项,選擇一種遷移方式執(zhí)行。
輕量遷移
當(dāng)你對(duì)本地庫改動(dòng)不大時(shí)鲜结,使用輕量遷移最快展运,符合輕量遷移的修改條件有:
- 添加或刪除屬性
- 非可選屬性變?yōu)榭蛇x屬性,反之亦然(但要填寫默認(rèn)值)
- 重命名實(shí)體精刷,屬性或關(guān)系
本地庫可以創(chuàng)建多個(gè)版本拗胜,選中.xcdatamodel文件 -> Editor -> Add Model Version
填入新版本名(Version name)、它的基礎(chǔ)版本(Based on model)
創(chuàng)建完成怒允,選擇使用你剛創(chuàng)建的版本
修改符合輕量遷移的修改條件埂软,比如增加一個(gè)表字段(height)
現(xiàn)在,我們可以直接編譯纫事、覆蓋安裝舊版本的App了勘畔,覆蓋后的App將擁有最新的本地庫版本,舊版本地庫的數(shù)據(jù)將自動(dòng)遷移到新版本丽惶,因?yàn)槟阒辉黾恿艘粋€(gè)表字段炫七,程序可以自動(dòng)識(shí)別如何將數(shù)據(jù)遷移到新版本。
輕量遷移前后的表結(jié)構(gòu)log
=========前=========
表名:
Student
字段:
id
age
name
=========后=========
表名:
Student
字段:
age
height
name
id
重量遷移
當(dāng)App版本不斷更新钾唬,本地庫的修改需求超過輕量遷移的能力范圍時(shí)万哪,使用重量遷移。
比如抡秆,現(xiàn)在需要新建一張Teacher表奕巍,表結(jié)構(gòu)與Student一致(id、age琅轧、name伍绳、height)乍桂,將原來的Student表里age超過18歲的人移到Teacher表,未超過的留在Student表效床。
具體步驟:
新建本地庫版本、添加Teacher表剩檀,字段與Student保持一致憋沿,并切換版本到最新
指定遷移計(jì)劃的方式沪猴,在Custom Policy屬性后填寫[工程名.類名]
新建的這個(gè)類讓它繼承NSEntityMigrationPolicy辐啄,并復(fù)寫createDestinationInstances方法
遷移時(shí)采章,每一條舊本地庫的數(shù)據(jù)都會(huì)調(diào)用一遍這個(gè)方法,sInstance就是舊數(shù)據(jù)壶辜,
manager可以獲取新舊版本本地庫的上下文
import Foundation
import CoreData
class V2_To_V3_Policy: NSEntityMigrationPolicy {
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
if sInstance.entity.name == "Student"
{
let id = sInstance.primitiveValue(forKey: "id") as! Int
let name = sInstance.primitiveValue(forKey: "name") as! String
let age = sInstance.primitiveValue(forKey: "age") as! Int
let height = sInstance.primitiveValue(forKey: "height") as! Int
if age > 18 {
let tea = NSEntityDescription.insertNewObject(forEntityName: "Teacher",
into: manager.destinationContext)
tea.setValue(id, forKey: "id")
tea.setValue(name, forKey: "name")
tea.setValue(age, forKey: "age")
tea.setValue(height, forKey: "height")
} else {
let stu = NSEntityDescription.insertNewObject(forEntityName: "Student",
into: manager.destinationContext)
stu.setValue(id, forKey: "id")
stu.setValue(name, forKey: "name")
stu.setValue(age, forKey: "age")
stu.setValue(height, forKey: "height")
}
}
}
}
與輕量遷移后一樣悯舟,直接編譯、覆蓋安裝舊版本的App了
啟動(dòng)后砸民,打印一下App的本地庫情況
表名:
Student
字段:
age
height
name
id
==================
表名:
Teacher
字段:
age
height
name
id
=================
學(xué)生數(shù)據(jù):
1. Alice1號(hào)
年齡: 6
身高:180
2. Alice2號(hào)
年齡: 7
身高:180
... ...
13. Alice13號(hào)
年齡: 18
身高:180
老師數(shù)據(jù):
14. Alice14號(hào)
年齡: 19
身高:180
15. Alice15號(hào)
年齡: 20
身高:180
... ...
20. Alice20號(hào)
年齡: 25
身高:180
漸進(jìn)式遷移
在不考慮輕量遷移的情況下抵怎,App從V1升級(jí)到V2,存在映射模型岭参、指定了遷移計(jì)劃反惕,遷移過程會(huì)很順利地完成,從V2升級(jí)到V3也是如此演侯。但是當(dāng)用戶很久沒更新姿染,直接從V1升級(jí)到V3時(shí),如果沒有專門寫V1到V3的遷移秒际,那么App不出意外會(huì)崩掉悬赏。這就意味著每一次升級(jí),你就需要為每一個(gè)歷史版本提供一份遷移到最新版本的計(jì)劃程癌。
使用漸進(jìn)式遷移舷嗡,每次只遷移到離當(dāng)前最近的一次版本,重復(fù)這個(gè)過程嵌莉,直到成為最新版本进萄。
待補(bǔ)充... ...
Relationship
使用CoreData你就不必再創(chuàng)建xx_id這種表字段了,這種字段可以使用relationship代替锐峭。
在xcdatamodel可視化界面中中鼠,relationships行有3個(gè)參數(shù):relationship/名字、destination/目標(biāo)表名沿癞、inverse/目標(biāo)表的relationships字援雇。
當(dāng)兩個(gè)表各自有一條relationships的destination和inverse指向?qū)Ψ剑徒⒁粭l關(guān)系椎扬,這個(gè)關(guān)系有一些參數(shù)可以選擇惫搏,比如是否級(jí)聯(lián)刪除、關(guān)系類型一對(duì)一或是一對(duì)多等蚕涤。
比如student表和teacher表建立了多對(duì)1的關(guān)系筐赔,那么student的數(shù)據(jù)行映射對(duì)象alice,可以直接使用像"alice.tea.name"獲取老師名字揖铜,其中tea是student表對(duì)teacher表的多對(duì)一關(guān)系名稱茴丰。
結(jié)尾
- 不要試圖刪除舊版本,要改動(dòng)的話新增版本就行。
- xcode默認(rèn)幫我們創(chuàng)建了表的同名類贿肩,因?yàn)楸淼腃odgen屬性默認(rèn)為Class Definition峦椰。
- 如果創(chuàng)建了版本,又使用git了回退汰规,發(fā)現(xiàn)項(xiàng)目編譯不了汤功。可以嘗試在文件管理器中的項(xiàng)目目錄下控轿,找到*.xcdatamodel文件冤竹,右鍵 -> 顯示包內(nèi)容 -> 刪除相關(guān)版本后再編譯。