削除されたコアデータオブジェクトへの参照を保持しているSwiftUIがクラッシュを引き起こす 質問する

削除されたコアデータオブジェクトへの参照を保持しているSwiftUIがクラッシュを引き起こす 質問する

コア データをビュー監視オブジェクト変数に渡すと、ナビゲーション リンク ビューはビューが消えた後でもオブジェクトへの参照を保持するため、SwiftUI でコア データを使用することは不可能であることがわかりました。そのため、コンテキストからオブジェクトを削除するとすぐに、エラー メッセージが表示されずにアプリがクラッシュします。

私は、コア データ オブジェクト変数をオプションとしてビュー モデルにラップし、コンテキスト削除アクションの直後にオブジェクトを nil に設定することでこれを確認しました。アプリは正常に動作しますが、コア データ オブジェクトを Swift UI ビューにバインドして真実のソースにする必要があるため、これは解決策ではありません。これはどのように動作するのでしょうか。どうやら、SwiftUI では、まったく複雑なものを作ることができないようです。

渡されたコア データ オブジェクトをオプションの @State に割り当てようとしましたが、うまくいきませんでした。フェッチされたオブジェクトなので、@Binding は使用できません。また、Swiftui コントロールにはバインディングが必要なので、変数も使用できません。@ObservedObject を使用するのが理にかなっています。ただし、これはオプションにできません。つまり、割り当てられたオブジェクトが削除されると、nil に設定できないため、アプリがクラッシュします。

以下は、デフォルトで監視可能なオブジェクトであるコア データ オブジェクトです。

class Entry: NSManagedObject, Identifiable {

    @NSManaged public var date: Date
}

これは、コア データ入力オブジェクトを別のビューに渡すビューです。

struct JournalView: View {

    @Environment(\.managedObjectContext) private var context

    @FetchRequest(
        entity: Entry.entity(),
        sortDescriptors: [],
        predicate: nil,
        animation: .default
    ) var entries: FetchedResults<Entry>

    var body: some View {
        NavigationView {
            List {
                ForEach(entries.indices) { index in
                    NavigationLink(destination: EntryView(entry: self.entries[index])) {
                        Text("Entry")
                    }
                }.onDelete { indexSet in
                    for index in indexSet {
                        self.context.delete(self.entries[index])
                    }
                }
            }
        }
    }
}

ここで、渡されたコア データ エントリ オブジェクトからすべての属性にアクセスするビューを示します。このエントリを任意のビューから削除すると、ここでまだ参照されているため、アプリがすぐにクラッシュします。これは、ナビゲーション リンクがアクセスされる前にすべての宛先ビューを初期化することにも関係していると思います。なぜそうするのか、意味がわかりません。これはバグですか、それともこれを実現するより良い方法がありますか?

onDisappear で削除を試みましたが、成功しませんでした。 JournalView から削除を実行しても、NavigationLink がオブジェクトを参照しているためクラッシュします。 興味深いことに、まだクリックされていない NavigationLink を削除するとクラッシュしません。

struct EntryView: View {

    @Environment(\.managedObjectContext) private var context
    @Environment(\.presentationMode) private var presentationMode

    @ObservedObject var entry: Entry

    var body: some View {
        Form {

            DatePicker(selection: $entry.date) {
                Text("Date")
            }

            Button(action: {
                self.context.delete(self.entry)
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Delete")
            }
        }
    }
}

アップデート

クラッシュにより、EntryView のエントリの最初の使用に移動し、スレッド 1: EXC_BAD_INSTRUCTION (コード = EXC_I386_INVOP、サブコード = 0x0) が読み取られます。これがスローされる唯一のメッセージです。

私が考えられる唯一の回避策は、コンテキストから削除しようとするのではなく、コア データ オブジェクト "isDeleted" にプロパティを追加して true に設定することです。その後、アプリを終了したとき、または起動したときに、isDeleted のすべてのエントリをクリーンアップして削除できますか? 理想的ではありません。ここでは何が間違っているのかを解明したいと思います。MasterDetailApp サンプルと何も違いがないように見えますが、これは機能しているようです。

ベストアンサー1

私も基本的に同じ問題を抱えていました。SwiftUI はすべてのビューを即座にロードするため、ビューには既存の CoreData オブジェクトのプロパティがロードされているようです。@ObservedObject 経由で一部のデータにアクセスするビュー内でこれを削除すると、クラッシュします。

私の回避策:

  1. 削除アクション - 延期されましたが、通知センター経由で終了しました
    Button(action: {
      //Send Message that the Item  should be deleted
       NotificationCenter.default.post(name: .didSelectDeleteDItem, object: nil)

       //Navigate to a view where the CoreDate Object isn't made available via a property wrapper
        self.presentationMode.wrappedValue.dismiss()
      })
      {Text("Delete Item")}

次のように Notification.name を定義する必要があります。

extension Notification.Name {

    static var didSelectDeleteItem: Notification.Name {
        return Notification.Name("Delete Item")
    }
}
  1. 適切なビューで、削除メッセージを探します

// Receive Message that the Disease should be deleted
    .onReceive(NotificationCenter.default.publisher(for: .didSelectDeleteDisease)) {_ in

        //1: Dismiss the View (IF It also contains Data from the Item!!)
        self.presentationMode.wrappedValue.dismiss()

        //2: Start deleting Disease - AFTER view has been dismissed
        DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(1)) {self.dataStorage.deleteDisease(id: self.diseaseDetail.id)}
    }

  1. 一部の CoreData 要素にアクセスするビューでは安全を確保してください - isFault をチェックしてください。

    VStack{
         //Important: Only display text if the disease item is available!!!!
           if !diseaseDetail.isFault {
                  Text (self.diseaseDetail.text)
            } else { EmptyView() }
    }

少しハッキーですが、私にとってはこれでうまくいきます。

おすすめ記事