写入数据 - SwiftUI
执行快速写入
除了在事务区块内执行写入之外,Realm Swift SDK 还提供了一项便利功能,无需显式执行写入事务即可实现快速写入。
使用 @ObservedRealmObject
或@ObservedResults
属性包装器时,可以隐式启动写事务。使用 $
操作符可创建与状态对象的双向绑定。然后,当您对绑定对象或集合进行更改时,您将启动隐式写入。
Realm SwiftUI 属性包装器可处理冻结的数据,以确保线程安全。当您使用 $
创建双向绑定时,Realm Swift SDK 会管理已冻结对象的解冻操作,以便您可以写入它们。
更新对象的属性
在本例中,我们使用状态对象的属性之一创建双向绑定。$dog.favoriteToy
用于创建与模型 Dog 对象的 favoriteToy
属性的绑定
当应用程序用户更新此示例中的该字段时,Realm 将打开隐式写入事务,并将新值保存到数据库中。
struct EditDogDetails: View { var dog: Dog var body: some View { VStack { Text(dog.name) .font(.title2) TextField("Favorite toy", text: $dog.favoriteToy) } } }
在 ObservedResults 集合中添加或删除对象
常规Realm结果集合是不可变的,而 ObservedResults是可变集合,允许您使用双向绑定执行写入。 当您更新绑定集合时, Realm会打开一个隐式写事务(write transaction),并将更改保存到集合中。
在此示例中,我们将使用 onDelete
中的 $dogs.remove
以从结果集中删除一个元素。此处使用 $dogs
会创建一个针对 BoundCollection
的双向绑定,而它可用于修改 @ObservedResults
dogs
集合。
我们使用 addDogButton
中的 $dogs.append
将项目添加到结果中。
这些动作会直接写入@ObservedResults
集合。
struct DogsListView: View { Dog.self) var dogs ( var body: some View { NavigationView { VStack { // The list shows the dogs in the realm. List { ForEach(dogs) { dog in DogRow(dog: dog) // Because `$dogs` here accesses an ObservedResults // collection, we can remove the specific dog from the collection. // Regular Realm Results are immutable, but you can write directly // to an `@ObservedResults` collection. }.onDelete(perform: $dogs.remove) }.listStyle(GroupedListStyle()) .navigationBarTitle("Dogs", displayMode: .large) .navigationBarBackButtonHidden(true) // Action bar at bottom contains Add button. HStack { Spacer() Button(action: { // The bound collection automatically // handles write transactions, so we can // append directly to it. This example assumes // we have some values to populate the Dog object. $dogs.append(Dog(value: ["name":"Bandido"])) }) { Image(systemName: "plus") } .accessibilityIdentifier("addDogButton") }.padding() } } } }
注意
@ObservedResults
属性包装器用于“SwiftUI 视图”。如果您想观察视图模型中的结果,请注册更改监听器。
将对象附加到列表
与具有列表属性的 @ObservedRealmObject
进行双向绑定时,可将新对象添加到列表中。
在本例中,Person
对象具有与一只或多只狗形成对多关系的列表属性。
class Person: Object, ObjectKeyIdentifiable { true) var _id: ObjectId (primaryKey: var firstName = "" var lastName = "" ... var dogs: List<Dog> }
当用户按下 Save
按钮时,会出现以下情况:
创建一个
Dog
对象,其中包含用户输入的详细信息将
Dog
对象添加到Person
对象的dogs
列表中
struct AddDogToPersonView: View { var person: Person var isInAddDogView: Bool var name = "" var breed = "" var weight = 0 var favoriteToy = "" var profileImageUrl: URL? var body: some View { Form { TextField("Dog's name", text: $name) TextField("Dog's breed", text: $breed) TextField("Dog's weight", value: $weight, format: .number) TextField("Dog's favorite toy", text: $favoriteToy) TextField("Image link", value: $profileImageUrl, format: .url) .keyboardType(.URL) .textInputAutocapitalization(.never) .disableAutocorrection(true) Section { Button(action: { let dog = createDog(name: name, breed: breed, weight: weight, favoriteToy: favoriteToy, profileImageUrl: profileImageUrl) $person.dogs.append(dog) isInAddDogView.toggle() }) { Text("Save") } Button(action: { isInAddDogView.toggle() }) { Text("Cancel") } } } } }
使用“创建”将对象复制到 Realm 中
有时,您可能会创建一个新对象,并将它的一个属性设为 Realm 中已存在的对象。然后,当您将新对象添加到 Realm 时,您就会看到类似以下内容的错误消息:
Object is already managed by another Realm. Use create instead to copy it into this Realm.
发生这种情况时,您可以使用.create 方法初始化该对象,并使用modified: .update
将其属性设置为现有对象。
例子
以 DoggoDB Dog
模型的一个版本为例。其中,favoriteToy
属性不只是一个 String
,而且是一个可选的 DogToy
对象:
class Dog: Object, ObjectKeyIdentifiable { true) var _id: UUID (primaryKey: var name = "" ... var favoriteToy: DogToy? ... }
当您的应用程序要创建新的 Dog
对象时,它可能会检查Realm 中是否已存在 DogToy
, 然后将 favoriteToy
属性设置为现有的 Dog Toy。
当您将新的 Dog
附加到 Person
对象时,您可能会看到类似以下内容的错误消息:
Object is already managed by another Realm. Use create instead to copy it into this Realm.
在将 Dog
对象附加到 Person
对象的 dogs
属性之前,该对象将将保持非托管状态。当 Realm Swift SDK 检查 Dog
对象以查找当前托管它的Realm 时,并未得到任何结果。
当您使用 $
表示法将 Dog
对象附加到 Person
对象中以执行快速写入时,写入操作会利用该视图可访问的 Realm。这是由 @ObservedRealmObject
或 @ObservedResults
属性包装器隐式打开的 Realm 实例。不过,现有 DogToy
对象可能由不同 Realm 实例托管。
要解决此错误,请使用.create 方法(当您初始化Dog
对象时),并使用modified: .update
设立其favoriteToy
值设置为现有对象:
// When working with an `@ObservedRealmObject` `Person`, this is a frozen object. // Thaw the object and get its realm to perform the write to append the new dog. let thawedPersonRealm = frozenPerson.thaw()!.realm! try! thawedPersonRealm.write { // Use the .create method with `update: .modified` to copy the // existing object into the realm let dog = thawedPersonRealm.create(Dog.self, value: ["name": "Maui", "favoriteToy": wubba], update: .modified) person.dogs.append(dog) }
执行显式写入
在某些情况下,您可能希望或需要显式执行写事务,而不是使用隐式 $
执行快速写入操作。在以下情况下,您可能希望执行此操作:
您需要查找其他对象才能执行写入
需要对视图中您没有访问权限的对象执行写入操作
如果您使用 @ObservedRealmObject
或 @ObservedResults
将观察中的 Realm 对象传递到一个函数中,并在该函数中执行显式写事务以修改对象时,您需要先将对象解冻。
let thawedCompany = company.thaw()!
您可以对对象或集合调用 .realm
,来访问管理对象的 Realm:
let realm = company.realm!.thaw()
由于 SwiftUI 属性包装器使用冻结对象,因此您必须先解冻 Realm,然后才能对其执行写入操作。
例子
考虑 DoggoDB 应用程序的一个版本,其中Company
对象具有 Employee
对象列表。每个 Employee
都有一个 Dog
对象列表。但出于业务原因,您还希望在 Company
对象上有一个可以直接使用的 Dog
对象列表,而不需要与 Employee
关联。该模型可能如下所示:
class Company: Object, ObjectKeyIdentifiable { true) var _id: ObjectId (primaryKey: var companyName = "" var employees: List<Employee> var dogs: List<Dog> }
以某一视图为例,您可以访问 Company
对象,但想执行显式写入以将现有犬添加到现有员工。您的函数可能如下所示:
// The `frozenCompany` here represents an `@ObservedRealmObject var company: Company` performAnExplicitWrite(company: frozenCompany, employeeName: "Dachary", dogName: "Maui") func performAnExplicitWrite(company: Company, employeeName: String, dogName: String) { // Get the realm that is managing the `Company` object you passed in. // Thaw the realm so you can write to it. let realm = company.realm!.thaw() // Thawing the `Company` object that you passed in also thaws the objects in its List properties. // This lets you append the `Dog` to the `Employee` without individually thawing both of them. let thawedCompany = company.thaw()! let thisEmployee = thawedCompany.employees.where { $0.name == employeeName }.first! let thisDog = thawedCompany.dogs.where { $0.name == dogName }.first! try! realm.write { thisEmployee.dogs.append(thisDog) } }