Firestore のラッパーライブラリ Ballcap-iOS を使っていた時に、思っていたのと違う挙動が起きたので、解消法についてメモします。
問題は何?
以下の View で recreate item
をタップすると vm.item
が更新され、 ChildView
の表示( item
の生成時刻 = item.dateDebugDescription
)が変わるはずですが、変わらないという問題です。
import SwiftUI
struct MyView: View {
@ObservedObject var vm = MyViewModel()
var body: some View {
VStack {
ChildView(item: vm.item)
Button("recreate item") { vm.recreateItem() }
}
}
struct ChildView: View {
@ObservedObject var item:Item
var body: some View {
Text(item.dateDebugDescription)
}
}
}
class MyViewModel: ObservableObject {
@Published var item:Item
init() {
let item = Item(id: "a")
item.dateDebugDescription = Date().debugDescription
self.item = item
}
func recreateItem() {
let item = Item(id: "a")
item.dateDebugDescription = Date().debugDescription
self.item = item
}
}
// GithubレポジトリのREADMEの「using-ballcap-with-swiftui」
// (https://github.com/1amageek/Ballcap-iOS#using-ballcap-with-swiftui)
// のクラス定義をコピーして
// クラス名を Item に変えて
// 「var dateDebugDescription = ""」を追加した
import Ballcap
import FirebaseFirestore
final class Item: Object, DataRepresentable, DataListenable, ObservableObject, Identifiable {
typealias ID = String
override class var name: String { "items" }
struct Model: Codable, Modelable {
var name: String = ""
}
@Published var data: Item.Model?
var listener: ListenerRegistration?
// MARK: -
var dateDebugDescription = ""
}
原因は何?
Ballcap の Object.swift に次のコードがありました。
public extension DataRepresentable where Self: Equatable, Self: Object {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.path == rhs.path && lhs.data == rhs.data
}
}
path
とdata
が一致していれば、インスタンスが異なっても==
が成立するみたいです。
ちなみに、
Item
クラスはDataRepresentable
プロトコルに準拠している。DataRepresentable
プロトコルはHashable
プロトコルに準拠している。Hashable
プロトコルはEquatable
プロトコルに準拠している。というわけで、
Item
クラスはEquatable
プロトコルに準拠しているみたいです。
MyView
の再描画時にChildView(item: vm.item)
の引数の新旧で==
が成り立っていることが原因かと思い色々調べると、そうでした。
解消法
少なくとも、Object.swift
、DataRepresentable.swift
、DataListenable.swift
では==
を使っていなかったので、==
の実装を上書きすることにしました。
インスタンス識別子を比較するようにします。
どこかにこのコードを書けばOKです。
public extension DataRepresentable where Self: Equatable, Self: Object {
static func == (lhs: Self, rhs: Self) -> Bool {
// インスタンス識別子で比較
ObjectIdentifier(lhs).hashValue == ObjectIdentifier(rhs).hashValue
}
}