zensehakittoneko
@zensehakittoneko

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

SwiftDataで1:多のモデルを定義した際のデータ複製について

解決したいこと

SwiftDataで1:多(one to many)の関係のモデルを定義しています。
画面ではリスト表示でGroupを表示して、リストをタップしたらGroupと紐づいているChartがリスト表示されます。
ディレクトリと内蔵ファイルのような関係です。

今回、Groupリストをスライドした際、そのGroupを複製する機能を実装しています。
しかし、下記のコードでその処理を実行するとアプリがクラッシュします。

クラッシュ箇所は@Relationshipのマクロ内のsetです。

この原因と解決法を教えていただきたいです。

下記のサイトでアプリの紹介をしているので、アプリをダウンロードいただけるとイメージが掴みやすいかもしれません。

発生している問題・エラー

import SwiftUI
import SwiftData


@Model
final class Group: Identifiable {
    @Attribute(.unique) var id: UUID
    var title: String
    var category: [String]
    @Relationship(deleteRule: .cascade, inverse: \Chart.group) var charts: [Chart]
    // ↓ここから
    {
        @storageRestrictions(accesses: _$backingData, initializes: _charts)
        init(initialValue) {
            _$backingData.setValue(forKey: \.charts, to: initialValue)
            _charts = _SwiftDataNoType()
        }
        get {
            _$observationRegistrar.access(self, keyPath: \.charts)
            return self.getValue(forKey: \.charts)
        }
        set {
            _$observationRegistrar.withMutation(of: self, keyPath: \.charts) {
                self.setValue(forKey: \.charts, to: newValue) // ここでクラッシュする(Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1cb459e90))
            }
        }
    }
    // ↑ここまではマクロの内容

    init(title: String, category: [String], charts: [Chart] = []) {
        self.id = UUID()
        self.title = title
        self.category = category
        self.charts = charts
    }

    func copy() -> Group {
        let copyGroup = Group(title: self.title, category: self.category)
        
        self.charts.forEach { originalChart in
            let copiedChart = originalChart.copy(group: copyGroup)
            copyGroup.charts.append(copiedChart)
        }
        
        return copyGroup
    }
}

@Model
final class Chart: Identifiable {
    var id: UUID
    var group: Group?
    var title: String
    var data: [String : Double]

    init(group: Group, title: String, data: [String : Double]) {
        self.id = UUID()
        self.group = group
        self.title = title
        self.data = data
    }

    func copy(group: Group) -> Chart {
        return Chart(group: group, title: self.title, data: self.data)
    }
}

複製機能の参考にしたサイト

試したこと

chartsの代入部分が問題なのかなと思い、Group.copyのcharts代入部分を何パターンか試しました。

    let copiedCharts = self.charts.map { originalChart in
        let copiedChart = originalChart.copy(group: copyGroup)
        return copiedChart
    }
    
    copyGroup.charts = copiedCharts
    copyGroup.charts = self.charts.map { $0.copy(group: copyGroup) }
0

2Answer

Comments

  1. スクリーンショット 2024-10-11 18.56.20.png
    クラッシュログがどこにあるか分からなかったのですが、画面キャプチャを添付します。
    また、画面の情報を見ていても何が何やらの状態です。。。

  2. 一旦 Xcode、シミュレータを閉じて、ターミナルから次のコマンドを実行後も同じエラーが起きますか?

    xcrun simctl spawn booted log config --mode "level:off"  --subsystem com.apple.CoreTelephony
    

  3. ご提示されたコマンドを実行し再度確認しました。
    前とは異なるエラーメッセージが表示されました。

    スクリーンショット 2024-10-12 15.57.11.png

  4. selfofだったりしませんか?

                _$observationRegistrar.withMutation(of: self, keyPath: \.charts) {
    -               self.setValue(forKey: \.charts, to: newValue) // ここでクラッシュする(Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1cb459e90))
    +               of.setValue(forKey: \.charts, to: newValue)
    
  5. self.setValueはマクロ内容なので編集できないと思います

  6. マクロを使わず書いてみたらどうでしょう?

  7. すみません。イメージがついていないので、もし可能でしたらコードを作成いただきたいですm(._.)m

  8. こちら解決しました。
    ご協力ありがとうございました!

下記のようにすることで解消しました。

    private func copyGroup(at index: Int) {
        // コピー元
        let originalGroup = groups[index]
        // Groupインスタンスを生成
        let newGroup = Group(title: originalGroup.title, category: originalGroup.category)
        // insert
        context.insert(newGroup)
        // Chart空配列を用意する
        var charts = [Chart]()
        // Group.chartsの数だけループ
        originalGroup.charts?.forEach { originalChart in
            // Chartインスタンスを生成
            let newChart = Chart(title: originalChart.title, data: originalChart.data)
            // Chart空配列にappend
            charts.append(newChart)
        }
        // GroupにChart配列をappend
        newGroup.charts?.append(contentsOf: charts)
        // save
        withAnimation {
            context.insert(newGroup)
        }
    }

また、これだとChartのgroupがnewGroupと紐づかないんじゃないか?と思ったのですが、デバッグしたところ、ちゃんと想定通りの関係性になっていました。
newGroupに追加する前後でpo charts[0].group?.idで確認しました。
スクリーンショット 2024-10-13 12.16.18.png

参考にしたサイト:
https://fatbobman.com/en/posts/relationships-in-swiftdata-changes-and-considerations/

0Like

Your answer might help someone💌