【问题标题】:SwiftUI holding reference to deleted core data object causing crashSwiftUI 持有对已删除核心数据对象的引用导致崩溃
【发布时间】:2020-03-26 03:39:07
【问题描述】:

我发现在 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 中的条目并读取 Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).. 这是唯一抛出的消息。

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

【问题讨论】:

  • 真是麻烦!这个@SybrSyn 有什么更新吗?!

标签: ios swift swiftui


【解决方案1】:

对我来说,我得到这个是因为强制解包绑定。

我使用了这样的Binding($item.someProperty)!TextField("Description", text: Binding($item.someProperty)!)

这是因为 item 是 Core Data 类,因此 somePropertyString? 而不是 StringBinding(*)!https://stackoverflow.com/a/59004832 中提出的解决方案。

按照https://stackoverflow.com/a/61002589 中的建议,我将实现更改为使用空合并运算符进行绑定,现在它不再崩溃了。

【讨论】:

    【解决方案2】:

    Apple 这么说(而且效果很好):

    您报告的行为是系统错误的结果,应该 在未来的版本中修复。作为一种解决方法,您可以防止 通过将删除逻辑包装在竞争条件中 NSManagedObjectContext.perform:

    private func deleteItems(offsets: IndexSet) {
    withAnimation {
        viewContext.perform {
            offsets.map { molts[$0] }.forEach(viewContext.delete)
            do {
                try viewContext.save()
            } catch {
                viewContext.rollback()
                userMessage = "\(error): \(error.localizedDescription)"
                displayMessage.toggle()
            }
        }
    }
    

    你可以在这里找到完整的帖子https://developer.apple.com/forums/thread/668299

    【讨论】:

      【解决方案3】:

      我有一段时间遇到同样的问题,对我来说解决方案非常简单: 在存储@ObservedObjectView 中,我只需将其放入!managedObject.isFault

      我只用ManagedObjectsdate 属性体验过这门课,我不知道这是否是崩溃验证的唯一情况。

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

      【讨论】:

        【解决方案4】:

        为此的视图修饰符(基于conditional view modifiers):

        import SwiftUI
        import CoreData
        
        extension View {
            @ViewBuilder
            func `if`<Transform: View>(
                _ condition: Bool,
                transform: (Self) -> Transform
            ) -> some View {
                if condition {
                    transform(self)
                } else {
                    self
                }
            }
        }
        
        extension View {
            func hidingFaults(_ object: NSManagedObject) -> some View {
                self.if(object.isFault) { _ in EmptyView() }
            }
        }
        

        话虽如此,有必要检查一下您是否在主线程上异步执行 CoreData 操作,同步执行操作可能会令人烦恼(有时,但并非总是如此)。

        【讨论】:

        • 您的意思是我们应该执行 viewContext.perform 以使其异步吗?仅此一项并不能解决崩溃问题。我不得不用你的解决方案隐藏错误。
        【解决方案5】:

        我已经尝试了所有以前的解决方案,但没有一个对我有用。

        这个,成功了。

        我的清单是这样的:

        List {
          ForEach(filteredItems, id: \.self) { item in
            ListItem(item:item)
          }
        .onDelete(perform: deleteItems)
        
        
        private func deleteItems(offsets: IndexSet) {
          //deleting items
        

        这是崩溃。

        我把代码改成了这个

        List {
          ForEach(filteredItems, id: \.self) { item in
            ListItem(item:item)
          }
          .onDelete { offsets in
             // delete objects
          }
        

        这可以正常工作而不会崩溃。

        看在上帝的份上,苹果!

        【讨论】:

          【解决方案6】:

          我最近遇到了同样的问题。将实体属性添加到视图修复它。

          ForEach(entities, id: \.self) { entity in
              Button(action: {
          
              }) {
                  MyCell(entity: entity)
              }
          }
          

          ForEach(entities, id: \.self) { entity in
              Button(action: {
          
              }) {
                  MyCell(entity: entity, property: entity.property)
              }
          }
          

          我怀疑可空的 Core Data 实体是问题的原因,而将非 nil 属性添加为 var(例如,var property: String)修复了它

          【讨论】:

          • 这对我有用。我将UserRow(user: entity) 更改为UserRow(user: entity, property: entity.contactID),这是一个字符串,我的ObservedObject 是entity,因此它将entity 和contactID 都传递给cellView,但我没有将ContactID 用于任何事情,它只是阻止它崩溃删除联系人后,列表会按应有的方式刷新。谢谢
          【解决方案7】:

          经过网上的一些研究,我很清楚这个崩溃可能是由许多与选项相关的事情引起的。对我来说,我意识到在 NSManagedObject 子类中将非可选核心数据属性声明为可选属性会导致问题。

          具体来说,我在 Core Data 中有一个 UUID 属性 id,它不能有默认值,但不是可选的。在我的子类中,我声明了@NSManaged public var id: UUID。将此更改为 @NSManaged public var id: UUID? 立即解决了问题。

          【讨论】:

            【解决方案8】:

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

            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 answer 提供.isFault 的提示!

            【讨论】:

            • 这是一个非常好的小解决方案。谢谢!
            • 切向注释,但最好将正文包裹在 Group 中并删除 AnyView 包装。例如:``` var body: some View { Group { if clip.isFault { EmptyView() } else { actualClipView } } } ``
            【解决方案9】:

            我基本上有同样的问题。似乎 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() }
                }
            
            

            有点hacky,但这对我有用。

            【讨论】:

            • 太棒了,感谢您提供此解决方案。我将不得不尝试解决这个问题。与此同时,我想出的解决方法是向名为“inTrash”的实体添加一个属性,并在删除时将其设置为 true,在获取请求中过滤掉垃圾并在启动时清理所有垃圾,并不理想,但这是也为我工作。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-09-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多