削除されたコアデータオブジェクトへの参照を保持しているSwiftUIがクラッシュの原因


8

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

コアデータオブジェクト変数をオプションとしてビューモデルにラップし、コンテキスト削除アクションの直後にオブジェクトをnilに設定してアプリが正常に動作することでこれを確認しましたが、コアデータオブジェクトが必要なため、これは解決策ではありません迅速な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でのエントリの最初の使用に私を連れて行き、Thread 1:EXC_BAD_INSTRUCTION(code = EXC_I386_INVOP、subcode = 0x0)を読み取ります。それがスローされる唯一のメッセージです。

考えられる唯一の回避策は、コアデータオブジェクト「isDeleted」にプロパティを追加し、コンテキストから削除しようとするのではなく、trueに設定することです。次に、アプリが終了したとき、または起動時に、isDeletedであるすべてのエントリをクリーンアップして削除できますか?理想的ではなく、ここで何が問題なのかを理解することを好むでしょう。私は、MasterDetailAppサンプルと何も変わらないように見えるので、機能しているようです。


なんて面倒なんだ!この@SybrSynの更新?!
Fattie

回答:


4

基本的に同じ問題がありました。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() }
    }

少しハッキーですが、これでうまくいきます。


すばらしい、この解決策をありがとう。これを試してみます。その間に私が思いついた回避策は、「inTrash」という名前のエンティティに属性を追加し、削除時にそれをtrueに設定し、フェッチリクエストでゴミをフィルタリングし、起動時にすべてのゴミをクリーンアップすることでしたが、これは理想的ではありませんが、これは私のためにも働いています。
SybrSyn

0

私は同じ問題に遭遇しましたが、根本的な問題の解決策は本当に見つかりませんでした。しかし、次のように、参照されたデータを使用するビューを「保護」します。

var body: some View {
    if (clip.isFault) {
        return AnyView(EmptyView())
    } else {
        return AnyView(actualClipView)
    }
}

var actualClipView: some View {
    // …the actual view code accessing various fields in clip
}

それもハックに感じますが、今のところうまくいきます。通知を使用して削除を「延期」するよりも複雑ではありませんが、ヒント付きのsTOOsの回答に感謝し.isFaultます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.