版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2020.10.19 星期一 |
前言
數(shù)據(jù)的持久化存儲(chǔ)是移動(dòng)端不可避免的一個(gè)問題蒲讯,很多時(shí)候的業(yè)務(wù)邏輯都需要我們進(jìn)行本地化存儲(chǔ)解決和完成治专,我們可以采用很多持久化存儲(chǔ)方案,比如說
plist
文件(屬性列表)蒸矛、preference
(偏好設(shè)置)舱卡、NSKeyedArchiver
(歸檔)骤坐、SQLite 3
、CoreData
油吭,這里基本上我們都用過击蹲。這幾種方案各有優(yōu)缺點(diǎn)署拟,其中,CoreData是蘋果極力推薦我們使用的一種方式歌豺,我已經(jīng)將它分離出去一個(gè)專題進(jìn)行說明講解推穷。這個(gè)專題主要就是針對(duì)另外幾種數(shù)據(jù)持久化存儲(chǔ)方案而設(shè)立。
1. 數(shù)據(jù)持久化方案解析(一) —— 一個(gè)簡單的基于SQLite持久化方案示例(一)
2. 數(shù)據(jù)持久化方案解析(二) —— 一個(gè)簡單的基于SQLite持久化方案示例(二)
3. 數(shù)據(jù)持久化方案解析(三) —— 基于NSCoding的持久化存儲(chǔ)(一)
4. 數(shù)據(jù)持久化方案解析(四) —— 基于NSCoding的持久化存儲(chǔ)(二)
5. 數(shù)據(jù)持久化方案解析(五) —— 基于Realm的持久化存儲(chǔ)(一)
6. 數(shù)據(jù)持久化方案解析(六) —— 基于Realm的持久化存儲(chǔ)(二)
7. 數(shù)據(jù)持久化方案解析(七) —— 基于Realm的持久化存儲(chǔ)(三)
8. 數(shù)據(jù)持久化方案解析(八) —— UIDocument的數(shù)據(jù)存儲(chǔ)(一)
9. 數(shù)據(jù)持久化方案解析(九) —— UIDocument的數(shù)據(jù)存儲(chǔ)(二)
10. 數(shù)據(jù)持久化方案解析(十) —— UIDocument的數(shù)據(jù)存儲(chǔ)(三)
11. 數(shù)據(jù)持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的數(shù)據(jù)存儲(chǔ)示例(一)
12. 數(shù)據(jù)持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的數(shù)據(jù)存儲(chǔ)示例(二)
13. 數(shù)據(jù)持久化方案解析(十三) —— 基于Unit Testing的Core Data測(cè)試(一)
14. 數(shù)據(jù)持久化方案解析(十四) —— 基于Unit Testing的Core Data測(cè)試(二)
開始
首先看下主要內(nèi)容:
在本
SwiftUI Realm
教程中类咧,您將通過構(gòu)建購物清單應(yīng)用程序來學(xué)習(xí)如何將Realm
與SwiftUI
一起用作數(shù)據(jù)持久性解決方案馒铃。內(nèi)容來自翻譯。
下面看下寫作環(huán)境:
Swift 5, iOS 14, Xcode 12
接著就是正文啦
Realm Mobile Database是一種流行的對(duì)象數(shù)據(jù)庫管理系統(tǒng)痕惋。它是開源的区宇,可以在多個(gè)平臺(tái)上使用。 Realm
的目標(biāo)是成為一種持久值戳,快速议谷,高性能,靈活和簡單的解決方案堕虹,同時(shí)仍可編寫類型安全的Swift
代碼卧晓。
SwiftUI
是Apple最新,最熱門的UI
框架赴捞。它使用聲明性語法來使用Swift代碼構(gòu)建視圖禀崖。當(dāng)用戶與其交互時(shí),它依靠狀態(tài)states
來被動(dòng)地更新其視圖螟炫。由于Realm
使用的Live Objects
也會(huì)自動(dòng)更新波附,因此混合使用這兩個(gè)框架很有意義!
在本SwiftUI領(lǐng)域教程中昼钻,您將學(xué)習(xí)如何:
- 設(shè)置
Realm
- 定義數(shù)據(jù)模型
- 對(duì)對(duì)象執(zhí)行基本的
CRUD
操作 - 將更改從數(shù)據(jù)庫轉(zhuǎn)到用戶界面
- 數(shù)據(jù)模型更改時(shí)處理遷移
您將通過在應(yīng)用程序中實(shí)現(xiàn)Realm
數(shù)據(jù)庫來學(xué)習(xí)所有這些知識(shí)掸屡。
注意:本
SwiftUI Realm
教程假定您已經(jīng)熟悉SwiftUI
。如果您剛剛開始使用SwiftUI然评,請(qǐng)查看我們的SwiftUI video course仅财。
打開啟動(dòng)文件夾中的PotionsMaster.xcodeproj
。
PotionsMaster
是一款可滿足您所有制藥需求的應(yīng)用程序碗淌。 藥水沖泡是一項(xiàng)具有挑戰(zhàn)性的技能盏求。 攪拌技術(shù),時(shí)間安排和裝瓶可能是艱巨的任務(wù)亿眠,即使對(duì)于經(jīng)驗(yàn)豐富的技師也是如此碎罚。 這個(gè)應(yīng)用可協(xié)助您追蹤所需的食材和已購買的食材。 使用PotionsMaster
纳像,即使您一直在閱讀的那個(gè)困難的新藥也將變得輕而易舉荆烈!
現(xiàn)在該開始工作了。 首先,構(gòu)建并運(yùn)行憔购。
PotionsMaster
是一個(gè)簡單的應(yīng)用程序宫峦,可讓您添加,更新和刪除列表中的成分玫鸟。但是當(dāng)您使用該應(yīng)用程序時(shí)导绷,會(huì)發(fā)現(xiàn)一個(gè)小問題。無論您做什么屎飘,該應(yīng)用程序都不會(huì)保留您的數(shù)據(jù)诵次!實(shí)際上,當(dāng)您嘗試創(chuàng)建枚碗,更新或刪除成分時(shí)逾一,它不會(huì)執(zhí)行任何操作。但請(qǐng)放心肮雨,您將使用Realm
使其發(fā)揮作用遵堵。
1. Project Structure
在深入研究并修復(fù)應(yīng)用程序以開始醞釀下一個(gè)藥水之前,請(qǐng)仔細(xì)閱讀入門項(xiàng)目怨规。它包含以下密鑰文件:
- Ingredient.swift:此結(jié)構(gòu)表示一種成分陌宿。
-
IngredientStore.swift:這是商店模式
(Store Pattern)
的類實(shí)現(xiàn)。此類負(fù)責(zé)存儲(chǔ)配料波丰。您還可以在其中對(duì)這些成分進(jìn)行操作壳坪。 -
IngredientsListView.swift:這是應(yīng)用程序的主視圖。此類顯示一個(gè)列表
List
掰烟。有一個(gè)區(qū)域Section
供您購買原料爽蝴,另一個(gè)區(qū)域供您購買原料。 -
IngredientFormView.swift:您將使用此
Form
來創(chuàng)建和更新成分纫骑。
該項(xiàng)目使用Store Pattern來處理狀態(tài)并將更改傳播到UI蝎亚。 IngredientStore
提供了一份食材清單,包括您需要購買的食材和已經(jīng)購買的食材先馆。當(dāng)用戶與應(yīng)用交互時(shí)发框,動(dòng)作會(huì)改變狀態(tài)state
。然后煤墙,SwiftUI
收到一個(gè)信號(hào)梅惯,以用新狀態(tài)更新UI。
Working with Realm
Realm
旨在解決當(dāng)今應(yīng)用程序的常見問題仿野。它提供了許多優(yōu)雅铣减,易于使用的解決方案。它可用于多個(gè)平臺(tái)设预,包括但不限于:
Swift/Objective-C
Java/Kotlin
JavaScript
.NET
關(guān)于Realm
的最酷的部分是這些技能是可以轉(zhuǎn)讓的徙歼。一旦以一種語言學(xué)習(xí)了Realm基礎(chǔ)知識(shí)犁河,就很容易用另一種語言來學(xué)習(xí)它們鳖枕。而且由于Realm
是跨平臺(tái)的數(shù)據(jù)庫魄梯,因此它的API
從一種語言到另一種語言的更改不會(huì)太大。
不過宾符,Realm
并不是萬能的數(shù)據(jù)庫酿秸。與SQLite
不同,Realm
是NoSQL
對(duì)象數(shù)據(jù)庫魏烫。像任何其他NoSQL
數(shù)據(jù)庫一樣辣苏,它也有優(yōu)點(diǎn)和缺點(diǎn)。但是Realm
是使多平臺(tái)團(tuán)隊(duì)保持同步的絕佳選擇哄褒。
1. Understanding the Realm Database
在開始設(shè)置Realm
之前稀蟋,您需要了解一些工作原理。
Realm
使用文件來保存和管理數(shù)據(jù)庫呐赡。應(yīng)用中的每個(gè)Realm
數(shù)據(jù)庫都稱為一個(gè)realm
退客。您的應(yīng)用程序可能有多個(gè)realm
,每個(gè)realm
都處理不同的對(duì)象域(domain of objects)
链嘀。這有助于使您的數(shù)據(jù)庫井井有條萌狂,簡潔明了。而且怀泊,由于跨平臺(tái)可用茫藏,因此您可以在iOS
和Android
等平臺(tái)之間共享預(yù)加載的Realm
文件。真的很有幫助霹琼,對(duì)吧务傲?
要打開Realm
文件,只需實(shí)例化一個(gè)新的Realm
對(duì)象枣申。如果您未傳遞自定義文件路徑树灶,則Realm
會(huì)在iOS上的Documents
文件夾中創(chuàng)建default.realm
文件。
有時(shí)糯而,您可能想使用一個(gè)realm
而不實(shí)際在磁盤上寫入數(shù)據(jù)天通。該數(shù)據(jù)庫為以下情況提供了便捷的解決方案: in-memory realms
。這些在編寫單元測(cè)試時(shí)很有用熄驼。您可以使用in-memory realms
來為每個(gè)測(cè)試用例預(yù)加載像寒,修改和刪除數(shù)據(jù),而無需實(shí)際在磁盤上進(jìn)行寫操作瓜贾。
Realm Mobile Database
不是Realm
提供的唯一產(chǎn)品诺祸。該公司還提供Realm Sync,這是一種用于在多個(gè)設(shè)備之間以及在云中同步Realm
數(shù)據(jù)庫的解決方案祭芦。此外筷笨,Realm提供了一個(gè)很棒的應(yīng)用程序來打開,編輯和管理您的數(shù)據(jù)庫:Realm Studio。
Setting up Realm
要開始使用Realm
胃夏,您必須將其作為依賴項(xiàng)包含在您的項(xiàng)目中轴或。 您可以使用許多依賴項(xiàng)管理工具,例如Swift Package Manager
和CocoaPods
仰禀。 本教程使用Swift Package Manager
照雁,但請(qǐng)隨意使用您更喜歡的工具。
注意:如果您不熟悉
Swift Package Manager
答恶,或者想了解更多信息饺蚊,請(qǐng)查看我們的Swift Package Manager for iOS tutorial教程。
要設(shè)置您的依賴關(guān)系悬嗓,請(qǐng)選擇File ? Swift Packages ? Add Package Dependency…
污呼。 復(fù)制以下內(nèi)容并粘貼到組合的搜索/輸入框中:
這是包含Realm
軟件包的GitHub
存儲(chǔ)庫的位置。 點(diǎn)擊Next
包竹。
接下來曙求,Xcode
要求您為此程序包定義一些選項(xiàng)。 只需保留Up To Next Major
的默認(rèn)值即可使用Realm
的最新版本映企。 (因此悟狱,在撰寫本文時(shí),此值從5.4.0
(含)到6.0.0
(不含)堰氓。)單擊Next
挤渐。
最后,選擇應(yīng)添加到項(xiàng)目中的包裝產(chǎn)品和target
双絮。 選擇Realm
和RealmSwift
浴麻,然后單擊Finish
。Xcode
從存儲(chǔ)庫下載代碼囤攀,并將Realm
包添加到PotionsMasrer target
软免。
構(gòu)建并運(yùn)行以確保一切正常。
設(shè)置好Realm
之后焚挠,就可以創(chuàng)建第一個(gè)data model
了膏萧。
Defining Your Realm Data Model
要構(gòu)建成分模型,請(qǐng)?jiān)?code>Models組中添加一個(gè)新的Swift
文件蝌衔,并將其命名為IngredientDB.swift
榛泛。 添加以下代碼:
import RealmSwift
// 1
class IngredientDB: Object {
// 2
@objc dynamic var id = 0
@objc dynamic var title = ""
@objc dynamic var notes = ""
@objc dynamic var quantity = 1
@objc dynamic var bought = false
// 3
override static func primaryKey() -> String? {
"id"
}
}
這是您剛添加的代碼的明細(xì):
- 1) 首先,定義您的類噩斟,繼承自
Object
曹锨,Realm
是所有數(shù)據(jù)模型的基類。您將使用該類將成分保存在Realm
中剃允。 - 2) 在這里沛简,您定義要
Realm
存儲(chǔ)的每個(gè)Ingredient
屬性齐鲤。 - 3) 最后,您重寫
primaryKey()
告訴Realm
哪個(gè)屬性是模型的主鍵椒楣。Realm
使用主鍵來強(qiáng)制唯一性给郊。主鍵還提供了一種獲取和更新數(shù)據(jù)的有效方法。
就這些撒顿!
您將Realm
數(shù)據(jù)模型定義為常規(guī)Swift類丑罪。 Realm使用這些類在磁盤上寫入數(shù)據(jù)荚板,但是對(duì)Object
子類有一些限制凤壁。由于跨平臺(tái)性質(zhì),Realm
僅支持有限的一組與平臺(tái)無關(guān)的屬性類型跪另。其中一些屬性是:
Bool
Int
Double
Float
String
Date
Data
Optional
屬性具有特殊限制拧抖。您可以將String,Date
和Data
聲明為可選免绿。其余部分唧席,使用包裝器類RealmOptional
;否則嘲驾,它們必須具有值淌哟。
每個(gè)屬性都有@objc dynamic
關(guān)鍵字。這使得它們可以在運(yùn)行時(shí)通過Dynamic Dispatch
訪問辽故。 Realm
使用此Swift
和Objective-C
功能在讀取和寫入數(shù)據(jù)之間創(chuàng)建外觀徒仓。訪問屬性時(shí),Swift
委托Realm
負(fù)責(zé)為您提供所需的數(shù)據(jù)誊垢。
1. Defining Relationships
Realm
還支持關(guān)系掉弛。您可以聲明嵌套對(duì)象以創(chuàng)建多對(duì)一關(guān)系。您可以使用List
創(chuàng)建多對(duì)多關(guān)系喂走。聲明列表時(shí)殃饿,Realm
會(huì)將那些嵌套對(duì)象與數(shù)據(jù)模型一起保存。
2. Adding an Initializer
在繼續(xù)之前芋肠,打開Ingredient.swift
并在文件底部添加以下擴(kuò)展名:
// MARK: Convenience init
extension Ingredient {
init(ingredientDB: IngredientDB) {
id = ingredientDB.id
title = ingredientDB.title
notes = ingredientDB.notes
bought = ingredientDB.bought
quantity = ingredientDB.quantity
}
}
此擴(kuò)展創(chuàng)建了一個(gè)方便的初始化程序乎芳,用于將IngredientDB
映射到Ingredient
。 您稍后將在項(xiàng)目中使用它帖池。
現(xiàn)在秒咐,您已經(jīng)定義了數(shù)據(jù)模型,是時(shí)候?qū)?duì)象添加到數(shù)據(jù)庫中了碘裕。
Adding Objects to the Database
在ViewModels
組中携取,打開IngredientStore.swift
。 通過在文件頂部添加以下行來導(dǎo)入RealmSwift
:
import RealmSwift
接下來帮孔,用以下代碼替換create(title:notes:quantity :)
的正文:
objectWillChange.send()
首先雷滋,您向SwiftUI
發(fā)送信號(hào)不撑。 由于IngredientStore
是一個(gè)ObservableObject
,因此SwiftUI
訂閱objectWillChange
并通過重新加載其視圖來響應(yīng)信號(hào)晤斩。
接下來焕檬,將以下代碼添加到方法中:
do {
let realm = try Realm()
let ingredientDB = IngredientDB()
ingredientDB.id = UUID().hashValue
ingredientDB.title = title
ingredientDB.notes = notes
ingredientDB.quantity = quantity
} catch let error {
// Handle error
print(error.localizedDescription)
}
首先,通過打開默認(rèn)Realm
創(chuàng)建一個(gè)Realm
實(shí)例澳泵。 使用它实愚,您可以編寫,讀取兔辅,更新和刪除對(duì)象腊敲。 接下來,創(chuàng)建一個(gè)IngredientDB
對(duì)象维苔,并根據(jù)方法的參數(shù)設(shè)置其屬性值碰辅。
您可以像其他任何Swift對(duì)象一樣實(shí)例化和使用Realm
數(shù)據(jù)模型。 您稱這些unmanaged objects
介时。 這意味著數(shù)據(jù)庫尚不了解它們没宾,并且任何更改都不會(huì)持久。 將對(duì)象添加到realm
后沸柔,該對(duì)象將由Realm
管理循衰。 這意味著Realm
將對(duì)象存儲(chǔ)在磁盤上并跟蹤其更改。
通過將以下幾行代碼添加到do
塊的末尾褐澎,將模型添加到realm
:
try realm.write {
realm.add(ingredientDB)
}
您可以通過在Realm
上調(diào)用write
來啟動(dòng)寫事務(wù)会钝。 您對(duì)realm
進(jìn)行的每個(gè)操作都必須在此寫事務(wù)塊(transaction block)
內(nèi),包括添加乱凿,刪除和更新顽素。 在事務(wù)內(nèi)部先较,您將新的IngredientDB
實(shí)例添加到Realm
询一。 Realm
現(xiàn)在正在存儲(chǔ)對(duì)象并跟蹤其更改账阻,使其成為managed object
班缰。
構(gòu)建并運(yùn)行檬某,然后繼續(xù)創(chuàng)建成分巩梢! 點(diǎn)擊New Ingredient
涯曲,為其命名芽卿,然后點(diǎn)擊保存寺枉。
但是抑淫,等等,仍然有些不對(duì)勁姥闪。 您創(chuàng)建了ingredient
始苇,但是什么也沒有發(fā)生! 它仍然列出與以前相同的ingredient
筐喳。
那是因?yàn)?code>IngredientsStore尚未從Realm
中fetch ingredients
- 它仍在使用mock
數(shù)據(jù)催式。 接下來函喉,您將解決此問題。
Fetching Objects
再次打開IngredientStore.swift
荣月,然后找到以下內(nèi)容:
var ingredients: [Ingredient] = IngredientMock.ingredientsMock
var boughtIngredients: [Ingredient] = IngredientMock.boughtIngredientsMock
使用下面替換上面代碼:
private var ingredientResults: Results<IngredientDB>
private var boughtIngredientResults: Results<IngredientDB>
從Realm
獲取對(duì)象時(shí)管呵,數(shù)據(jù)庫將返回Results
類型。 此類型表示從查詢檢索到的對(duì)象的集合哺窄。
現(xiàn)在捐下,在剛添加的兩行下面添加以下初始化程序:
// 1
init(realm: Realm) {
// 2
ingredientResults = realm.objects(IngredientDB.self)
.filter("bought = false")
// 3
boughtIngredientResults = realm.objects(IngredientDB.self)
.filter("bought = true")
}
以下是上述初始化程序中的操作:
- 1) 首先,您將收到一個(gè)
Realm
實(shí)例萌业。 您將使用此實(shí)例來獲取ingredients
坷襟。 - 2) 接下來,您從
realm
中fetch ingredient
咽白,并用bought
為false
過濾它們啤握。 - 3) 然后鸟缕,您從
realm
中fetch ingredient
晶框,并用bought
為true
過濾它們。
最后懂从,在初始化程序之后插入以下代碼:
var ingredients: [Ingredient] {
ingredientResults.map(Ingredient.init)
}
var boughtIngredients: [Ingredient] {
boughtIngredientResults.map(Ingredient.init)
}
這些屬性將Realm
的Result
轉(zhuǎn)換為常規(guī)數(shù)組授段。 示例項(xiàng)目的UI使用這些計(jì)算出的屬性將數(shù)據(jù)庫模型映射到視圖。
由于IngredientStore
現(xiàn)在在其初始值設(shè)定項(xiàng)中需要一個(gè)Realm
番甩,因此需要提供它侵贵。 打開ScenceDelegate.swift
。 在import SwiftUI
語句之后缘薛,通過插入以下內(nèi)容導(dǎo)入RealmSwift
:
import RealmSwift
接下來窍育,將scene(_:willConnectTo:options:)
中的代碼更改為此:
if let windowScene = scene as? UIWindowScene {
do {
// 1
let realm = try Realm()
let window = UIWindow(windowScene: windowScene)
// 2
let contentView = ContentView()
.environmentObject(IngredientStore(realm: realm))
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
} catch let error {
// Handle error
fatalError("Failed to open Realm. Error: \(error.localizedDescription)")
}
}
這是您正在做的事情:
- 1) 您創(chuàng)建一個(gè)新的
Realm
實(shí)例。 - 2) 您可以使用
Realm
實(shí)例實(shí)例化IngredientStore
宴胧,并將其添加到ContentViews
環(huán)境中漱抓。
構(gòu)建并運(yùn)行。 現(xiàn)在創(chuàng)建一個(gè)ingredient
恕齐,看看魔術(shù)乞娄!
Realm
與Live Objects
一起使用。 當(dāng)您向Realm
中添加ingredient
時(shí)显歧,IngredientResults
會(huì)自動(dòng)更新仪或,而無需每次都獲取它。 SwiftUI
收到信號(hào)以使用新的最新視圖更新UI士骤。 感覺像魔術(shù)范删,對(duì)不對(duì)? 繼續(xù)創(chuàng)造更多ingredients
拷肌!
現(xiàn)在您可以成功添加ingredients
了到旦,該構(gòu)建用于更新現(xiàn)有ingredients
的功能的時(shí)候了束铭。
Updating Objects
PotionsMaster
的一項(xiàng)關(guān)鍵功能是能夠?qū)⒊煞智袚Q到BOUGHT
列表。 現(xiàn)在厢绝,如果您點(diǎn)擊購買按鈕契沫,則什么也不會(huì)發(fā)生。 要解決此問題昔汉,您可以使用Realm
更新磁盤上的成分懈万。
1. Toggling Ingredients to BOUGHT
要將ingredient
移至BOUGHT
列表,您需要在磁盤上將bought
的屬性值更新為true
靶病。
打開IngredientStore.swift
并將toggleBought(ingredient :)
的內(nèi)容替換為以下內(nèi)容:
// 1
objectWillChange.send()
do {
// 2
let realm = try Realm()
try realm.write {
// 3
realm.create(
IngredientDB.self,
value: ["id": ingredient.id, "bought": !ingredient.bought],
update: .modified)
}
} catch let error {
// Handle error
print(error.localizedDescription)
}
這是這段代碼中發(fā)生的事情:
- 1) 您向
SwiftUI
發(fā)送一個(gè)信號(hào)会通,指示該對(duì)象即將更改。 - 2) 您打開默認(rèn)
realm
娄周。 - 3) 您開始一個(gè)新的寫事務(wù)并調(diào)用
create(_:value:update :)
涕侈,并傳遞更新的值和.modified
的case
。 這告訴Realm
使用字典中的值更新數(shù)據(jù)庫煤辨。 建立字典時(shí)裳涛,您必須包含對(duì)象的id
。 如果具有該id
的對(duì)象已經(jīng)存在众辨,Realm
將使用新值更新它端三。 否則,Realm
將在磁盤上創(chuàng)建一個(gè)新對(duì)象鹃彻。
構(gòu)建并運(yùn)行郊闯。 現(xiàn)在,通過點(diǎn)擊其單元格右側(cè)的圓形圖標(biāo)來購買一種ingredient
蛛株。
在更新bought
時(shí)团赁,Realm
會(huì)將此更改同時(shí)通知IngredientResults
和buyIngredientResults
。 更新的ingredient
將移至buyedIngredientResults
谨履。 SwiftUI
在列表List
上添加動(dòng)畫效果欢摄! 多么酷啊屉符?
更新對(duì)象的另一種方法是將其屬性設(shè)置為新值剧浸。 同樣,您可以在寫事務(wù)中執(zhí)行此操作矗钟。 然后唆香,Realm
將更新磁盤上的每個(gè)值。
2. Updating Other Properties
現(xiàn)在您知道如何更新對(duì)象吨艇,可以使用Realm
輕松更新其他屬性躬它。 將update(ingredientID:title:notes:quantity :)
的正文更改為以下代碼:
// 1
objectWillChange.send()
do {
// 2
let realm = try Realm()
try realm.write {
// 3
realm.create(
IngredientDB.self,
value: [
"id": ingredientID,
"title": title,
"notes": notes,
"quantity": quantity
],
update: .modified)
}
} catch let error {
// Handle error
print(error.localizedDescription)
}
這是這段代碼中發(fā)生的事情:
- 1) 再次,您使用
objectWillChange
發(fā)送一個(gè)信號(hào)东涡,告訴SwiftUI
重新加載UI冯吓。 - 2) 您打開默認(rèn)
realm
倘待。 - 3) 您在寫事務(wù)中調(diào)用
create(_:value:update :)
。 此調(diào)用將更新ingredient
的值组贺。
這類似于您上面添加的用于購買ingredient
的代碼凸舵。 您調(diào)用create(_:value:update :)
,并傳遞更新后的值失尖。 Realm
會(huì)更新磁盤上的值啊奄,并將更改的結(jié)果通知ingredientResults
。 然后掀潮,SwiftUI
使用這些更改更新UI
菇夸。
構(gòu)建并運(yùn)行。 點(diǎn)按一種ingredient
的名稱以再次打開該表格仪吧。 編輯一些字段庄新,然后點(diǎn)擊Update
。
現(xiàn)在薯鼠,剩下的就是刪除ingredients
了择诈!
Deleting Objects
輕按buy
按鈕可將配料移至BOUGHT
部分。 但是一旦它存在就無法擺脫它人断。 輕按垃圾桶圖標(biāo)不會(huì)執(zhí)行任何操作吭从。
要解決此問題朝蜘,請(qǐng)打開IngredientStore.swift
恶迈。 用以下代碼替換delete(ingredientID :)
的正文:
// 1
objectWillChange.send()
// 2
guard let ingredientDB = boughtIngredientResults.first(
where: { $0.id == ingredientID })
else { return }
do {
// 3
let realm = try Realm()
try realm.write {
// 4
realm.delete(ingredientDB)
}
} catch let error {
// Handle error
print(error.localizedDescription)
}
以下是刪除此代碼中ingredient
的方法:
- 1) 再次,您使用
objectWillChange
發(fā)送信號(hào)谱醇,請(qǐng)求SwiftUI
重新加載UI暇仲。 - 2) 您找到要從
buyedIngredientResults
中刪除的ingredient
。 - 3) 您打開默認(rèn)
realm
副渴。 - 4) 最后奈附,調(diào)用
delete
,傳遞要?jiǎng)h除的對(duì)象煮剧。
構(gòu)建并運(yùn)行斥滤。 購買一種ingredient
,然后點(diǎn)擊刪除按鈕將其從列表中刪除勉盅。
Adding a New Property to a Realm Object
在開發(fā)過程中佑颇,數(shù)據(jù)模型會(huì)不斷增長和發(fā)展。 屬性類型可能會(huì)更改草娜,并且您可能需要添加或刪除屬性挑胸。 使用Realm
,更改數(shù)據(jù)模型就像更改任何其他Swift類一樣容易宰闰。
在本部分中茬贵,您將添加一個(gè)新屬性簿透,以按顏色識(shí)別ingredients
。
打開IngredientDB.swift
并在bought
項(xiàng)下添加一個(gè)新屬性:
@objc dynamic var colorName = "rw-green"
接下來解藻,在Ingredient.swift
中老充,添加以下屬性:
var colorName = "rw-green"
您還需要更新初始化程序以設(shè)置colorName
。 在文件的初始化程序的底部添加以下行:
colorName = ingredientDB.colorName
在上面的三行代碼中螟左,您添加了一個(gè)屬性蚂维,用于將顏色名稱存儲(chǔ)在Realm
上并在視圖中進(jìn)行映射。
就這些路狮! 這些模型已準(zhǔn)備好存儲(chǔ)顏色名稱虫啥。 接下來,您將更新IngredientStore.swift
將此新屬性保存在數(shù)據(jù)庫中奄妨。
1. Storing the New Property in Realm
打開IngredientStore.swift
涂籽,找到以下代碼:
func create(title: String, notes: String, quantity: Int) {
使用下面代碼替換:
func create(title: String, notes: String, quantity: Int, colorName: String) {
現(xiàn)在,在設(shè)置其他屬性(如quantity
和 notes
)之后插入以下行:
ingredientDB.colorName = colorName
這將添加一個(gè)新參數(shù)colorName
砸抛,并將其分配給IngredientDB
评雌。
仍然在IngredientStore.swift
中,找到以下行:
func update(ingredientID: Int, title: String, notes: String, quantity: Int) {
將它更換為:
func update(
ingredientID: Int,
title: String,
notes: String,
quantity: Int,
colorName: String
) {
最后直焙,在update
中景东,找到這個(gè)代碼:
realm.create(
IngredientDB.self,
value: [
"id": ingredientID,
"title": title,
"notes": notes,
"quantity": quantity
],
update: .modified)
使用下面代碼進(jìn)行替換:
realm.create(
IngredientDB.self,
value: [
"id": ingredientID,
"title": title,
"notes": notes,
"quantity": quantity,
"colorName": colorName
],
update: .modified)
此代碼將參數(shù)colorName
添加到更新中。 調(diào)用create(_:value:update :)
時(shí)奔誓,它將其添加到value
字典中斤吐。
現(xiàn)在,create
和update
方法都需要一個(gè)colorName
厨喂。
但是Xcode
認(rèn)識(shí)到IngredientFormFormView
在調(diào)用這些方法時(shí)沒有傳遞colorName
和措,并且會(huì)產(chǎn)生一些錯(cuò)誤。
要解決此問題蜕煌,請(qǐng)打開IngredientForm.swift
派阱。 在屬性quantity
之后添加以下代碼:
@Published var color = ColorOptions.rayGreen
現(xiàn)在找到init(_:ingredient :)
并在底部添加以下行:
color = ColorOptions(rawValue: ingredient.colorName) ?? .rayGreen
在此處,您添加了一個(gè)屬性斜纪,用于在用戶創(chuàng)建或更新ingredient
時(shí)存儲(chǔ)顏色贫母。
接下來,打開IngredientFormView.swift
并在saveIngredient()
中找到以下代碼:
store.create(
title: form.title,
notes: form.notes,
quantity: form.quantity)
替換它使用下面:
store.create(
title: form.title,
notes: form.notes,
quantity: form.quantity,
colorName: form.color.name)
在updateIngredient()
中盒刚,您需要在調(diào)用中傳遞colorName
進(jìn)行update
腺劣。 為此,請(qǐng)找到以下代碼:
store.update(
ingredientID: ingredientID,
title: form.title,
notes: form.notes,
quantity: form.quantity)
替換上面伪冰,使用下面:
store.update(
ingredientID: ingredientID,
title: form.title,
notes: form.notes,
quantity: form.quantity,
colorName: form.color.name)
現(xiàn)在誓酒,您已經(jīng)解決了IngredientFormFormView
不將colorName
傳遞給IngredientStore
的問題。
構(gòu)建并運(yùn)行。 你得到...
該應(yīng)用程序崩潰了靠柑! Realm
引發(fā)遷移錯(cuò)誤:Migration is required due to the following errors:
寨辩,但是為什么會(huì)這樣呢?
Working With Migrations
啟動(dòng)應(yīng)用程序時(shí)歼冰,Realm
會(huì)在您的代碼中掃描帶有Object
子類的類靡狞。找到一個(gè)模型后,它將創(chuàng)建一個(gè)用于將模型映射到數(shù)據(jù)庫的schema
隔嫡。
當(dāng)您更改數(shù)據(jù)模型時(shí)甸怕,new schema
與數(shù)據(jù)庫中的schema
將不匹配。如果發(fā)生這種情況腮恩,Realm
會(huì)引發(fā)錯(cuò)誤梢杭。您必須告訴Realm
如何將舊schema
遷移到新schema
。否則秸滴,它不知道如何將舊對(duì)象映射到新schema
武契。
由于您向IngredientDB
添加了新屬性colorName
,因此必須為其創(chuàng)建遷移荡含。
注意:您可以在開發(fā)期間通過實(shí)例化
Realm.Configuration
時(shí)將true
傳遞給deleteRealmIfMigrationNeeded
來解決咒唆。這告訴Realm
,如果需要遷移释液,它應(yīng)該刪除其文件并創(chuàng)建一個(gè)新文件全释。
1. Creating a Migration
在Models
組中,創(chuàng)建一個(gè)名為RealmMigrator.swift
的文件误债。
現(xiàn)在浸船,將此代碼添加到新文件中:
import RealmSwift
enum RealmMigrator {
// 1
static private func migrationBlock(
migration: Migration,
oldSchemaVersion: UInt64
) {
// 2
if oldSchemaVersion < 1 {
// 3
migration
.enumerateObjects(ofType: IngredientDB.className()) { _, newObject in
newObject?["colorName"] = "rw-green"
}
}
}
}
細(xì)目如下:
- 1) 您定義遷移方法。 該方法接收遷移對(duì)象和
oldSchemaVersion
找前。 - 2) 您檢查文件固定
schema
的版本糟袁,以確定要運(yùn)行的遷移。 每個(gè)schema
都有一個(gè)版本號(hào)躺盛,從零開始。 在這種情況下形帮,如果舊schema
是第一個(gè)模式(在添加新屬性之前)槽惫,請(qǐng)運(yùn)行遷移。 - 3) 最后辩撑,為
Realm
中的每個(gè)舊的和新的IngredientDB
對(duì)象界斜,為新屬性分配一個(gè)默認(rèn)值。
Realm
使用migrationBlock
來運(yùn)行遷移并更新任何必要的屬性合冀。
在RealmMigrator
的底部各薇,添加以下新的static
方法:
static func setDefaultConfiguration() {
// 1
let config = Realm.Configuration(
schemaVersion: 1,
migrationBlock: migrationBlock)
// 2
Realm.Configuration.defaultConfiguration = config
}
這是您在這段代碼中所做的:
- 1) 您可以使用
migrationBlock
創(chuàng)建一個(gè)Realm.Configuration
的新實(shí)例,并將該schema
的當(dāng)前版本設(shè)置為1
。 - 2) 您設(shè)置了
Realm
的新默認(rèn)配置峭判。
最后开缎,在SceneDelegate.swift
中,在scene(_:willConnectTo:options:)
頂部調(diào)用此新方法:
RealmMigrator.setDefaultConfiguration()
Realm
使用此配置來打開默認(rèn)數(shù)據(jù)庫林螃。 發(fā)生這種情況時(shí)奕删,Realm
會(huì)檢測(cè)到文件持久化schema
與新schema
之間的不匹配。 然后疗认,它通過運(yùn)行剛創(chuàng)建的遷移功能來遷移更改完残。
現(xiàn)在構(gòu)建并再次運(yùn)行。 這次崩潰不見了横漏!
您已成功將新屬性添加到IngredientDB
谨设。 您已經(jīng)做好了遷移的準(zhǔn)備。 現(xiàn)在是時(shí)候更新表格缎浇,以便用戶選擇顏色了铝宵!
Adding a New Field to the Form
打開IngredientFormView.swift
并找到注釋// TODO: Insert Picker here
。 將此代碼插入注釋行下方:
Picker(selection: $form.color, label: Text("Color")) {
ForEach(colorOptions, id: \.self) { option in
Text(option.title)
}
}
這會(huì)將新的picker view
添加到IngredientFormView
华畏。 該picker
使用戶可以選擇顏色鹏秋。
接下來,打開IngredientRow.swift
并找到注釋// TODO: Insert Circle view here
亡笑。 在注釋后添加以下代碼:
Circle()
.fill(Color(ingredient.colorName))
.frame(width: 12, height: 12)
在這里侣夷,您要向每個(gè)ingredient
行添加一個(gè)圓形視圖。 您用該ingredient
的顏色填充圓圈仑乌。
構(gòu)建并運(yùn)行以查看更改百拓。 現(xiàn)在創(chuàng)建一個(gè)新ingredient
并為其選擇顏色。
很好晰甚! 現(xiàn)在衙传,您可以繼續(xù)列出要釀造的特殊藥水所需的所有成分。
在本SwiftUI Realm教程中厕九,您學(xué)習(xí)了如何使用SwiftUI從Realm創(chuàng)建蓖捶,更新,獲取和刪除對(duì)象扁远。 除了基礎(chǔ)知識(shí)之外俊鱼,您還了解了遷移以及如何創(chuàng)建遷移。
要了解有關(guān)Realm
的更多信息畅买,可以參考其official documentation并闲。
如果您想了解更多關(guān)于SwiftUI的信息,請(qǐng)參閱我們的SwiftUI by Tutorials.谷羞。
后記
本篇主要講述了基于
Realm
和SwiftUI
的數(shù)據(jù)持久化簡單示例帝火,感興趣的給個(gè)贊或者關(guān)注~~~