SwiftUI 持有对已删除核心数据对象的引用,导致崩溃

Syb*_*Syn 30 ios swift swiftui

我发现无法在 SwiftUI 中使用核心数据,因为当我将核心数据传递给视图观察对象变量时,即使在视图消失后,导航链接视图也会保留对该对象的引用,因此一旦我删除了应用程序崩溃的上下文中的对象,没有错误消息。

我已经通过将核心数据对象变量作为可选变量包装到视图模型中来确认这一点,然后在上下文删除操作之后将对象设置为 nil 并且应用程序工作正常,但这不是解决方案,因为我需要核心数据对象绑定到 swift ui 视图并成为事实的来源。这应该如何工作?我真的不能用 SwiftUI 使任何远程复杂的东西看起来。

我尝试将传入的核心数据对象分配给可选的@State,但这不起作用。我不能使用 @Binding 因为它是一个获取的对象。而且我不能使用变量,因为 swiftui 控件需要绑定。使用@ObservedObject 才有意义,但这不能是可选的,这意味着当分配给它的对象被删除时,应用程序崩溃,因为我无法将其设置为 nil。

这里是核心数据对象,默认是一个可观察对象:

class Entry: NSManagedObject, Identifiable {

    @NSManaged public var date: Date
}
Run Code Online (Sandbox Code Playgroud)

这是一个将核心数据条目对象传递给另一个视图的视图。

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])
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在这里是从传入的核心数据条目对象访问所有属性的视图。有一次,我从任何视图中删除了这个条目,顺便说一下,它仍然在这里被引用并导致应用程序立即崩溃。我相信这也与导航链接在访问它们之前初始化所有目标视图有关。为什么它会这样做是没有意义的。这是一个错误,还是有更好的方法来实现这一目标?

我什至尝试删除 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")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新

崩溃使我第一次使用 EntryView 中的 entry 并读取 Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)..这是抛出的唯一消息。

我能想到的唯一解决方法是向核心数据对象“isDeleted”添加一个属性并将其设置为 true 而不是尝试从上下文中删除。然后当应用程序退出或启动时,我可以清除并删除所有已删除的条目吗?不理想,并且更愿意弄清楚这里出了什么问题,因为看起来我没有做任何与 MasterDetailApp 示例不同的事情,这似乎有效。

Luc*_*uca 11

我有一段时间遇到同样的问题了,对我来说解决方案非常简单:在存储的View位置中@ObservedObject,我只需将这个!managedObject.isFault.

ManagedObjects我仅通过属性体验过此类date,我不知道这是否是崩溃验证的唯一情况。

import SwiftUI

struct Cell: View {
    
    @ObservedObject var managedObject: MyNSManagedObject
    
    var body: some View {
        if !managedObject.isFault {
           Text("\(managedObject.formattedDate)")
        } else {
            ProgressView()
        }
    }
}

Run Code Online (Sandbox Code Playgroud)


sTO*_*OOs 8

我基本上有同样的问题。看起来 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")}

Run Code Online (Sandbox Code Playgroud)

您需要定义一个 Notification.name,例如:

extension Notification.Name {

    static var didSelectDeleteItem: Notification.Name {
        return Notification.Name("Delete Item")
    }
}
Run Code Online (Sandbox Code Playgroud)
  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)}
    }

Run Code Online (Sandbox Code Playgroud)
  1. 在访问某些 CoreData 元素的视图中保持安全 - 检查 isFault!

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

Run Code Online (Sandbox Code Playgroud)

有点hacky,但这对我有用。


小智 5

我遇到了同样的问题,并没有真正找到解决根本问题的方法。但现在我“保护”使用引用数据的视图,如下所示:

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
}
Run Code Online (Sandbox Code Playgroud)

这也感觉很糟糕,但现在工作正常。它比使用通知“推迟”删除要简单,但仍然感谢sTOOs对提示的回答.isFault