SwiftData 1:N で削除の挙動観測隊
構造
次のようなアプリがきっかけでこの観測を行ったので、記事全体でこの構造を基本にしている。
モデルはA、B、C
複数のA、複数のCがあり、AとCを結びつけるBがあります。結びつき方の情報が必要なので間にBが必要です。結びつき方を具体的にイメージしたい場合は「AとCの友好度4」とか「敵対度5」のようなものでよいでしょう。Bは頻繁に作成と削除が行われ、AとCは、Bと比べると作成と削除の頻度は低いものです。
- Aは、0か1かいくつかのBを配列で持つ
- Bは、nilか1つのAを持つ
- Bは、nilか1つのCを持つ
- Cは、0か1かいくつかのBを配列で持つ
Aを削除した時のB削除のルールは .cascade
、連鎖してBを削除します。
Cを削除した時のB削除のルールは .cascade
、連鎖してBを削除します。
Bを削除した時のA削除のルールは .nullify
、AはBの保持を捨てます。
Bを削除した時のC削除のルールは .nullify
、AはBの保持を捨てます。
コード
import SwiftUI
import SwiftData
@Model
class A {
@Attribute(.unique)
var id = UUID().uuidString
var name: String
@Relationship(deleteRule: .cascade)
var b: [B] = []
init(name: String) {
self.name = name
}
}
@Model
class B {
@Attribute(.unique)
var id = UUID().uuidString
var name: String
@Relationship(deleteRule: .nullify, inverse: \A.b)
var a: A?
@Relationship(deleteRule: .nullify, inverse: \C.b)
var c: C?
init(name: String) {
self.name = name
}
}
@Model
class C {
@Attribute(.unique)
var id = UUID().uuidString
var name: String
@Relationship(deleteRule: .cascade)
var b: [B] = []
init(name: String) {
self.name = name
}
}
あるbに関連するものを取り出すと基本の状態は次のようになります。
【テーマ1】 本体削除と配列の中身削除は挙動が違うか
aを削除したとき
modelContext.delete(a)
bがmodelContextから削除される
cのB配列中のbが削除される
bを削除したとき
modelContext.delete(b)
aのB配列中のbが削除される
cのB配列中のbが削除される
cを削除したとき
aと同様
aのbを空配列にする
a.b = []
bはmodelContextから削除されず
b.aがnilになる
cのbを空配列にする
aと同様
aのbからremoveAllで排除する
a.b.removeAll()
bはmodelContextから削除されず
そのb.aがnilになる
cのbからremoveAllで排除する
aと同様
【テーマ2】 a.bをc.bにコピー後の挙動
aのbをcのbにコピーしたとき
c.b = a.b
コピー前
コピー後
ちゃんとbからcが見えてます。
aのbをcのbにコピーしたあと、aを削除したとき
modelContext.delete(a)
bがmodelContextから削除される
cのB配列中のbが削除される
aのbをcのbにコピーしたあと、bをmodelContextから削除したとき
modelContext.delete(b)
aのB配列中のbが削除される
cのB配列中のbが削除される
aのbをcのbにコピーしたあと、aのbを空配列にしたとき
a.b = []
bはmodelContextから削除されず
b.aがnilになる
a.bをc.bにコピーすることに関して、何か特別なことが起きるかなと思って観測したが、特に何もなかった。
【テーマ3】 a1.bをa2.bにコピーしたとき
a2.b = a1.b
コピー前
コピー後
- a1.bはクリア
- a2.bは意図通り
- b.aはa2
なんと a1.bがクリアされる (B配列中のbが削除される)。
【テーマ4】 a1のbをa2のbにも登録したとき
a1が関係していることはコードに書かれていない。
if let b = listB.first {
a2.b.append(b)
}
a2のb登録前
a2のb登録後
- a1.bはクリア
- a2.bは意図通り
- b.aはa2
こちらも a1.bがクリアされる 。
【テーマ5】 すでにbが設定されているa2のbにa1のbをコピーしたとき
a2.b = a1.b
代入前
代入後
- a1.bはクリア
- a2.bは意図通り
- b1.aはa2
- b2.aはnil
結構複雑ですけど やってくれます 。
他の記事
デリートルールを調査しました。
Discussion