使用SwiftUI从coredata中删除记录时出现崩溃

7
我从coredata中列出音频项目的列表。在删除后,崩溃报告显示为“EXC_BREAKPOINT (code=1, subcode=0x1b8fb693c)”,为什么?
在使用时,

ForEach(items, id: \.self)

它运行正常。但是我的音频具有id属性,并遵循可识别协议。

更新:我发现添加if {}语句将修复崩溃,但为什么?在"static UUID.unconditionallyBridgeFromObjectiveC(:) ()"处设置断点。

struct Test1View: View {
    @Environment(\.managedObjectContext) var context
    @FetchRequest(fetchRequest: Audio.fetchAllAudios()) var items: FetchedResults<Audio>
    var body: some View {
        List {
            ForEach(items) { item in
                if true { // <- this if clause fix crash, but why?
                    HStack {
                        Text("\(item.name)")
                    }
                }
            }.onDelete(perform: { indexSet in
                let index = indexSet.first!
                let item = self.items[index]
                self.context.delete(item)
                try? self.context.save()
            })
        }
    }
}

以下是代码:

class Audio: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID
    @NSManaged public var name: String
    @NSManaged public var createAt: Date
}

struct Test1View: View {
    @Environment(\.managedObjectContext) var context
    var fetchRequest: FetchRequest<Audio> = FetchRequest<Audio>(entity: Audio.entity(), sortDescriptors: [NSSortDescriptor(key: "createAt", ascending: false)])
    var items: FetchedResults<Audio> { fetchRequest.wrappedValue }
    var body: some View {
        List {
            ForEach(items) { item in
                HStack {
                    Text("\(item.name)")
                }
            }.onDelete(perform: { indexSet in
                let index = indexSet.first!
                let item = self.items[index]
                self.context.delete(item)
                try? self.context.save()
            })
        }
    }
}
3个回答

15

我和你一样也遇到了同样的问题。

使用自己的id属性来使其成为Identifiable可能是一个坏主意,因为当对象被删除时,Core Data会将所有属性设置为nil,即使在你的Swift CoreData类中声明方式不同。
删除实体时,id属性被失效,对象的.isFault属性被设置为true,但是 SwiftUI ForEach仍然持有对此ID对象(=UUID)的某些引用,以便能够计算列表的“之前”和“之后”状态,并尝试访问它,导致崩溃。

因此,以下建议:

  1. 通过检查isFault来保护详细视图(在ForEach循环中):
if entity.isFault {
    EmptyView()
}
else {
    // your regular view body
}
  1. 期望您的id属性为nil,可以在核心数据模型中将其定义为可选项。
@NSManaged public var id: UUID?

或者通过在 SwiftUI ForEach 循环中 不依赖 Identifiable 协议:

ForEach(entities, id: \.self) { entity in ... }
或者
ForEach(entities, id: \.objectID) { entity in ... }
结论:你真的不需要将所有的CoreData属性都设为SwiftOptional。重要的是,在ForEach循环中引用的id属性可以优雅地处理删除(即将其值设置为nil)。

很好的解释。谢谢。 - alionthego
谢谢您分享您的解决方案。您是如何想到这个方法的?我怀疑视图中可能有问题,但我无法确定具体是哪里出了问题。 - brontus
干得好!这个问题困扰了我一阵。使用滑动删除功能很不错,但在其他情境下它会失败。改用 objectID 属性后,效果非常好! - Jbryson

3

我发现了崩溃的原因,是由于OC/Swift对象转换时必须提供可选项:

转换

class Audio: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID
    @NSManaged public var name: String
    @NSManaged public var createAt: Date
}

为了

class Audio: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID?
    @NSManaged public var name: String?
    @NSManaged public var createAt: Date?
}

你救了我!我花了30分钟来解决这个错误,现在问题解决了 :) - DanielZanchi
谢谢!对我来说,只需要将ID设置为可选项就足够了。 - Denis Kakačka

2

我在周末也遇到了同样的问题。看起来 SwiftUI 想要解开从 CoreData 读取的值,而由于该值已被删除,因此会导致崩溃。

在我的情况下,我通过对从 CoreData 使用的所有值进行 nil coalescing 来解决了这个问题。

您可以尝试为 item.name 提供默认值

ForEach(items) { item in
            HStack {
                Text("\(item.name ?? "")")
            }
        }

我有另外一段代码,和这个示例代码完全一样,可以正常工作。我感到困惑。使用 ForEach(items, id: \.self) 可以修复这段代码。如果这真的是 SwiftUI 中 CoreData 的问题,那将是可怕的。我在想是否在这段代码中犯了一些错误。 - foolbear
问题是 UUID._unconditionallyBridgeFromObjectiveC,空指针。添加一个 if true {} 子句将修复崩溃,但为什么?ForEach(items) { item in if true { HStack { Text("\(item.name)") } } } - foolbear

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接