删除UI中展示的Realm对象时避免‘无效对象’异常
在UI中展示Realm数据库对象列表时,添加条目功能正常,但点击“Delete all”删除所有条目时应用崩溃,抛出异常:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
即使注释掉Text("\(entry.name)")仍会崩溃,相关实现代码如下:
视图代码
struct EntriesListView: View { @StateObject private var model = Model() var body: some View { VStack { List { ForEach($model.entries, id: \.self) { entry in Text("\(entry.name)") } } Button("Add dummy entry") { Task { await model.addDummyEntry() } } Button("Delete all") { Task { await model.deleteAll() } } }.onAppear { self.model.load() }.onDisappear { self.model.unload() } } }
Model代码
@MainActor public class Model: ObservableObject { var realm: Realm? @Published var entries: [EntryRealmModel] = [] var entriesToken: NotificationToken? = nil init() { do { realm = try Realm() } catch { realm = nil } } private func loadEntries() -> [EntryRealmModel] { guard let realm = realm else { return [] } let results = realm.objects(EntryRealmModel.self) return Array(results) } func load() { guard let realm = realm else { return } self.entries = self.loadEntries() entriesToken = realm.observe { (notification, realm) in self.entries = self.loadEntries() } } func unload() { entries = [] entriesToken?.invalidate() } func addDummyEntry() async { guard let realm = realm else { return } let entry = EntryRealmModel( // ... ) do { try await realm.asyncWrite { realm.add(entry) } } catch { } } func deleteAll() async { guard let realm = realm else { return } do { try await realm.asyncWrite { realm.delete(realm.objects(EntryRealmModel.self)) } } catch { } } }
崩溃原因
绑定失效对象触发访问:你使用了
ForEach($model.entries, id: \.self),这会让SwiftUI绑定到EntryRealmModel对象本身。当所有条目被Realm删除后,这些对象会被标记为失效,但SwiftUI的绑定系统仍可能持有对它们的引用,尝试访问时直接触发崩溃——哪怕注释掉Text(entry.name),绑定本身的存在就会导致SwiftUI尝试读取已失效的对象。通知更新存在时序差:虽然你监听了Realm全局通知,但
asyncWrite删除操作完成后,通知触发并更新entries为空数组的时机,可能晚于SwiftUI尝试渲染旧的失效对象的时机,进而导致崩溃。
修复方案
方案1:改用只读ForEach,避免绑定失效对象
如果不需要修改列表中的条目,直接使用只读形式的ForEach,不绑定对象本身:
List { ForEach(model.entries, id: \.id) { entry in // 假设EntryRealmModel有稳定的唯一id字段,也可继续用\.self Text("\(entry.name)") } }
如果确实需要绑定(比如修改条目),可以先判断对象有效性:
ForEach($model.entries, id: \.id) { $entry in if entry.isInvalidated { EmptyView() } else { Text("\(entry.name)") } }
方案2:使用Realm Results替代普通数组(推荐)
Realm的Results是自动更新的集合,能自动处理对象失效和数据同步,比手动维护数组更可靠。修改Model如下:
@MainActor public class Model: ObservableObject { var realm: Realm? @Published var entries: Results<EntryRealmModel>? // 改为Results类型 var entriesToken: NotificationToken? = nil init() { do { realm = try Realm() } catch { realm = nil } } func load() { guard let realm = realm else { return } self.entries = realm.objects(EntryRealmModel.self) entriesToken = entries?.observe { [weak self] _ in // Results自动更新,只需触发视图刷新 self?.objectWillChange.send() } } func unload() { entries = nil entriesToken?.invalidate() } // addDummyEntry、deleteAll方法保持不变 }
视图中对应修改:
List { if let entries = model.entries { ForEach(entries, id: \.id) { entry in Text("\(entry.name)") } } }
方案3:删除前先清空本地数组(临时规避)
在deleteAll中先清空本地数组,让UI先更新为空,再执行Realm删除操作:
func deleteAll() async { guard let realm = realm else { return } // 先清空本地数组,触发UI提前更新 self.entries = [] do { try await realm.asyncWrite { realm.delete(realm.objects(EntryRealmModel.self)) } } catch { } }
内容的提问来源于stack exchange,提问作者cheersmate

