數(shù)據(jù)持久化方案解析(十五) —— 基于Realm和SwiftUI的數(shù)據(jù)持久化簡單示例(一)

版本記錄

版本號(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 3CoreData油吭,這里基本上我們都用過击蹲。這幾種方案各有優(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í)如何將RealmSwiftUI一起用作數(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不同,RealmNoSQL對(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)可用茫藏,因此您可以在iOSAndroid等平臺(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 ManagerCocoaPods仰禀。 本教程使用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)容并粘貼到組合的搜索/輸入框中:

https://github.com/realm/realm-cocoa.git

這是包含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双絮。 選擇RealmRealmSwift浴麻,然后單擊FinishXcode從存儲(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,DateData聲明為可選免绿。其余部分唧席,使用包裝器類RealmOptional;否則嘲驾,它們必須具有值淌哟。

每個(gè)屬性都有@objc dynamic關(guān)鍵字。這使得它們可以在運(yùn)行時(shí)通過Dynamic Dispatch訪問辽故。 Realm使用此SwiftObjective-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尚未從Realmfetch 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) 接下來,您從realmfetch ingredient咽白,并用boughtfalse過濾它們啤握。
  • 3) 然后鸟缕,您從realmfetch ingredient晶框,并用boughttrue過濾它們。

最后懂从,在初始化程序之后插入以下代碼:

var ingredients: [Ingredient] {
  ingredientResults.map(Ingredient.init)
}

var boughtIngredients: [Ingredient] {
  boughtIngredientResults.map(Ingredient.init)
}

這些屬性將RealmResult轉(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ù)乞娄!

RealmLive 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 :)涕侈,并傳遞更新的值和.modifiedcase。 這告訴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í)通知IngredientResultsbuyIngredientResults。 更新的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è)置其他屬性(如quantitynotes)之后插入以下行:

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)在,createupdate方法都需要一個(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.谷羞。

后記

本篇主要講述了基于RealmSwiftUI的數(shù)據(jù)持久化簡單示例帝火,感興趣的給個(gè)贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子犀填,更是在濱河造成了極大的恐慌蠢壹,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宏浩,死亡現(xiàn)場(chǎng)離奇詭異知残,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)比庄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門求妹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人佳窑,你說我怎么就攤上這事制恍。” “怎么了神凑?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵净神,是天一觀的道長。 經(jīng)常有香客問我溉委,道長鹃唯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任瓣喊,我火速辦了婚禮坡慌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藻三。我一直安慰自己洪橘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布棵帽。 她就那樣靜靜地躺著熄求,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逗概。 梳的紋絲不亂的頭發(fā)上弟晚,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音仗谆,去河邊找鬼指巡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛隶垮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秘噪,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼狸吞,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蹋偏,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤便斥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后威始,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枢纠,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年黎棠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晋渺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脓斩,死狀恐怖木西,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情随静,我是刑警寧澤八千,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站燎猛,受9級(jí)特大地震影響恋捆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜重绷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一沸停、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧论寨,春花似錦星立、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至火焰,卻和暖如春劲装,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昌简。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工占业, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纯赎。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓谦疾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親犬金。 傳聞我的和親對(duì)象是個(gè)殘疾皇子念恍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348