如何結(jié)合 Core Data 和 SwiftUI
本文轉(zhuǎn)載自微信公眾號「Swift社區(qū)」,作者韋弦Zhy 。轉(zhuǎn)載本文請聯(lián)系Swift社區(qū)公眾號。
core data stack
SwiftUI 和 Core Data 之間相差將近十年 —— SwiftUI 隨著 iOS 13 面世而 Core Data 則是 iPhoneOS 3 的產(chǎn)物;很久以前,它還沒有被稱為 iOS,因為 iPad 尚未發(fā)布。盡管時間相距遙遠,Apple 還是投入了大量工作以確保這兩種強大的技術(shù)能夠完美地相互配合使用,這意味著 Core Data 就像始終以這種方式設(shè)計一樣,已集成到 SwiftUI 中。
在此項目中,我們將僅使用少量 Core Data 的功能,但是這種功能將很快擴展——我只想首先了解一下它。當您創(chuàng)建 Xcode 項目時,我要求您選中 Use Core Data 框,它應(yīng)該導(dǎo)致對項目的更改:
- 現(xiàn)在,您有了一個名為 Bookworm.xcdatamodeld 的文件。這描述了您的數(shù)據(jù)模型,該數(shù)據(jù)模型實際上是類及其屬性的列表。
- AppDelegate.swift 和 SceneDelegate.swift 中現(xiàn)在有用于設(shè)置 Core Data 的額外代碼。
設(shè)置核心數(shù)據(jù)需要兩個步驟:創(chuàng)建所謂的持久性容器(從容器存儲中加載并保存實際數(shù)據(jù)),然后將其注入 SwiftUI 環(huán)境中,以便我們所有的視圖都可以訪問它。
Xcode 模板已經(jīng)為我們完成了這兩個步驟。
因此,剩下的就是我們要決定要在 Core Data 中存儲哪些數(shù)據(jù),以及如何讀出這些數(shù)據(jù)。首先,我們需要打開 Bookworm.xcdatamodeld 并開始使用 Xcode 的模型編輯器描述我們的數(shù)據(jù)。
之前我們描述過這樣的數(shù)據(jù):
- struct Student {
- var id: UUID
- var name: String
- }
但是,Core Data 不能那樣工作。您會看到,Core Data 需要提前知道我們所有數(shù)據(jù)類型的樣子,包含的內(nèi)容以及它們之間的關(guān)系。這就是 “xcdatamodeld” 文件的來源:我們將類型定義為“實體”,然后在其中創(chuàng)建屬性作為“屬性”,Core Data 負責將其轉(zhuǎn)換為可以在運行時使用的實際數(shù)據(jù)庫布局。
為了進行試用,請點擊 “Add Entity” 按鈕創(chuàng)建一個新實體,然后雙擊其名稱將其重命名為 “Student”。接下來,單擊 “Attributes”表正下方的+按鈕以添加兩個屬性:“id”作為 UUID 和 “name” 作為字符串。這將告訴 Core Data 創(chuàng)建學(xué)生并保存他們所需的一切,因此請回到 ContentView.swift,以便我們編寫一些代碼。
使用獲取請求從 Core Data 中檢索信息——我們描述了我們想要的內(nèi)容,應(yīng)如何對其進行排序以及是否應(yīng)使用任何過濾器,然后 Core Data 會發(fā)回所有匹配的數(shù)據(jù)。我們需要確保該獲取請求隨著時間的推移保持最新,以便在創(chuàng)建或刪除學(xué)生時,我們的 UI 保持同步。
SwiftUI 有一個解決方案,而且——您猜對了——這是另一個屬性包裝器。這次將其稱為@FetchRequest,它帶有兩個參數(shù):我們要查詢的實體以及我們希望結(jié)果如何排序。它具有非常特定的格式,因此,我們首先為學(xué)生添加獲取請求——請立即將此屬性添加到 ContentView:
- @FetchRequest(entity: Student.entity(), sortDescriptors: []) var students: FetchedResults<Student>
分解之后,這創(chuàng)建了一個獲取的“學(xué)生”實體的請求,不進行任何排序,而是將其放入名稱為students,類型為FetchedResults
從那里開始,我們可以像常規(guī)的 Swift 數(shù)組一樣開始使用學(xué)生,但是您會發(fā)現(xiàn)有一個陷阱。首先,一些將數(shù)組放入List的代碼:
- var body: some View {
- VStack {
- List {
- ForEach(students, id: \.id) { student in
- Text(student.name ?? "Unknown")
- }
- }
- }
- }
你發(fā)現(xiàn)異常了嗎?是的,student.name是可選的——它可能有一個值,也可能沒有。這是 Core Data 的一個領(lǐng)域,該領(lǐng)域會讓您大為惱火:它具有可選數(shù)據(jù)的概念,但與 Swift 的可選數(shù)據(jù)完全不同。如果我們對 Core Data 說“這不是必須的”(您可以在模型編輯器中完成),它仍然會生成可選的 Swift 屬性,因為所有 Core Data 關(guān)心的是屬性在保存時具有值——在其他時間它們可以為 nil。
您可以根據(jù)需要運行代碼,但沒有太多意義——該列表將為空,因為我們尚未添加任何數(shù)據(jù),因此我們的數(shù)據(jù)庫為空。為了解決這個問題,我們將在列表下方創(chuàng)建一個按鈕,每次點擊都會添加一個新的隨機學(xué)生,但是首先我們需要一個新屬性來存儲托管對象上下文。
讓我重申一下,因為這很重要。當我們定義 “Student” 實體時,實際上發(fā)生的是 Core Data 為我們創(chuàng)建了一個類,該類繼承自其自身的一個類:NSManagedObject。我們無法在代碼中看到該類,因為它是在構(gòu)建項目時自動生成的,就像 Core ML 的模型一樣。這些對象之所以稱為托管對象,是因為 Core Data 會照料它們:它從持久性容器中加載它們并將它們的更改也寫回。
我們所有的托管對象都位于托管對象上下文中,該上下文負責實際獲取托管對象以及保存更改等。如果需要的話,您可以有許多托管對象上下文,但這距離現(xiàn)在還有一段路要走——實際上,您可以長期使用它。
我們不需要創(chuàng)建此托管對象上下文,因為 Xcode 已經(jīng)為我們創(chuàng)建了一個。更好的是,它已經(jīng)將其添加到 SwiftUI 環(huán)境中,這就是@FetchRequest屬性包裝器起作用的原因——它使用了環(huán)境中可用的任何托管對象上下文。
因此,現(xiàn)在將此屬性添加到ContentView:
- @Environment(\.managedObjectContext) var moc
設(shè)置好之后,下一步是添加一個按鈕,該按鈕生成隨機的學(xué)生并將其保存在托管對象上下文中。為了幫助學(xué)生脫穎而出,我們將通過創(chuàng)建firstNames和lastNames數(shù)組來分配隨機名稱,然后使用randomElement()從中選擇一個。
首先在List下方添加此按鈕:
- Button("Add") {
- let firstNames = ["Ginny", "Harry", "Hermione", "Luna", "Ron"]
- let lastNames = ["Granger", "Lovegood", "Potter", "Weasley"]
- let chosenFirstName = firstNames.randomElement()!
- let chosenLastName = lastNames.randomElement()!
- // more code to come
- }
**注意:**不可避免地有人會抱怨我強行對randomElement()調(diào)用,但是實際上我們只是手工創(chuàng)建了具有值的數(shù)組——它將永遠成功。如果您非常討厭強制拆包,則可以將其替換為空合計算和默認值。
現(xiàn)在,有趣的部分是:我們將使用為我們生成的 Core Data 類創(chuàng)建一個 Student對象。這需要附加到托管對象上下文中,以便對象知道應(yīng)將其存儲在何處。然后,我們可以像通常為結(jié)構(gòu)體那樣分配值。
因此,現(xiàn)在將這三行添加到按鈕的操作閉包中:
- let student = Student(context: self.moc)
- student.id = UUID()
- student.name = "\(chosenFirstName) \(chosenLastName)"
最后,我們需要詢問托管對象上下文以保存自身。這是一個引發(fā)函數(shù)的調(diào)用,因為理論上它可能會失敗。實際上,我們所做的一切都沒有失敗的可能,因此我們可以使用try?來調(diào)用它——–我們不在乎捕獲錯誤。
因此,請將最后一行添加到按鈕的操作中:
- try? self.moc.save()
最后,您現(xiàn)在應(yīng)該可以運行該應(yīng)用程序并對其進行嘗試——單擊幾次 “Add” 按鈕以生成一些隨機的學(xué)生,您應(yīng)該看到他們滑入我們列表的某個位置。更好的是,如果您重新啟動該應(yīng)用程序,您會發(fā)現(xiàn)學(xué)生還在,因為 Core Data 已保存了他們。
現(xiàn)在,您可能認為這需要大量的學(xué)習,但并不會帶來很多結(jié)果,但是您現(xiàn)在知道什么是實體和屬性,知道什么是托管對象和請求,并且已經(jīng)了解了如何保存更改。在此項目的后面以及將來,我們都將更多地關(guān)注 Core Data,但到目前為止,您已經(jīng)走了很遠。



























