SwiftData: 別のモデルを配列として持つとよく分からない動きになる件

SwiftDataで、あるモデルに別のモデルのインスタンスを配列として持たせる、というようなことをします。
@Model
class ParentData {
var name: String
var timestamp: Date
var children: [ChildData]
init(name: String, timestamp: Date = Date.now, children: [ChildData]) {
self.name = name
self.timestamp = timestamp
self.children = children
}
}
@Model
class ChildData {
var name: String
init(name: String) {
self.name = name
}
}
サンプル作るときにあまり考えずに、名前を「親子」にしてしまいましたが、記事を書く段階で別に親子じゃないなと思ったりしますが、ともかくParentData
がChildData
を配列として知っている、というような感じです。
これを、ビューに表示する際、ForEach
で表示します。ParentData
もChildData
もForEach
で回します。
struct ContentView: View {
@Query(sort: [
SortDescriptor(\ParentData.timestamp),
SortDescriptor(\ParentData.name)]) var parents: [ParentData]
@Environment(\.modelContext) var modelContext
var body: some View {
VStack {
List {
ForEach(parents) { parent in
VStack {
HStack {
let date = parent.timestamp.formatted(
Date.FormatStyle(date: .numeric, time: .shortened)
.second(.twoDigits))
Text("\(parent.name)@\(date)")
.font(.headline)
Spacer()
}
.padding(8)
.background(Color(white: 0.8))
ForEach(parent.children) { child in
HStack {
Spacer()
Text(child.name)
.padding(.leading, 24)
}
}
}
}
}
.listStyle(.plain)
Divider()
Button("Add") {
addParent()
}
.buttonStyle(.borderedProminent)
}
}
private func addParent() {
do {
let childA = ChildData(name: "childA")
let childB = ChildData(name: "childB")
let parentX = ParentData(name: "parentX", children: [childA, childB])
let parentY = ParentData(name: "parentY", children: [childB])
modelContext.insert(childA)
modelContext.insert(childB)
modelContext.insert(parentX)
modelContext.insert(parentY)
try modelContext.save()
}
catch {
print("error: \(error.localizedDescription)")
}
}
}
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(for: [ParentData.self, ChildData.self])
}
}
}
「Add」ボタンを押すと、childA、childB、parentX、parentYが作られます。
問題は以下の2行です。
let parentX = ParentData(name: "parentX", children: [childA, childB])
let parentY = ParentData(name: "parentY", children: [childB])
parentXは、childAとchildB、parentYはchildBだけを持つというような初期化なんですよね。
(持つ、というのとは違うのかも知れませんが。関連を保持する、ですかね)
ですが実際に実行してみると、期待通りの結果にはなりません。
21:44:49に追加したparentX,Yは、childA、Bを1個ずつ持っています。
21:44:51に追加したparentX,Yは、XがchildA、Bを両方持ち、Yは何も持ちません。
21:50:01、21:50:06もこの繰り返しのようですね。
繰り返すたびにBがX,Yの間をフラフラしますが、XがA,B両方持ったうえでYがBを持つ、というのは何度繰り返しても現れません。
A,Bは一回ずつしか現れないんですね。

ちなみに以下のように書くと、期待通りに表示されます。
private func addParent() {
do {
let childA = ChildData(name: "childA")
let childB = ChildData(name: "childB")
let childB2 = ChildData(name: "childB")
let parentX = ParentData(name: "parentX", children: [childA, childB])
let parentY = ParentData(name: "parentY", children: [childB2])
modelContext.insert(childA)
modelContext.insert(childB)
modelContext.insert(childB2)
modelContext.insert(parentX)
modelContext.insert(parentY)
try modelContext.save()
}
catch {
print("error: \(error.localizedDescription)")
}
}

先日遭遇してスクラップを書いた、「.unique
属性をつけてインサートすると、既存のレコードに配列がマージされる」という動きもそうですが、配列というか1:多の関連というか、これの動きが全体的に(私の)直感からずれているんですね。
なんとなく、裏側の動きがどうなってるのか調べないといかんのかなと思い始めています。

結局、SwiftDataでは暗黙の関連は、1:1もしくは1:Nしか定義されない、ということですね。たぶん。
多対多の関連を定義する場合、子の側に、親への配列を持たせる必要がある、ということですね。おそらく。
以下の記事の「N : N のリレーションを定義する」「Explicit relationship(明示的なリレーション)」の部分。
分かるけど、分かりたくない…
面倒くさい…