You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

删除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 { }
    }
}

问题原因及解决方案

崩溃原因

  1. 绑定失效对象触发访问:你使用了ForEach($model.entries, id: \.self),这会让SwiftUI绑定到EntryRealmModel对象本身。当所有条目被Realm删除后,这些对象会被标记为失效,但SwiftUI的绑定系统仍可能持有对它们的引用,尝试访问时直接触发崩溃——哪怕注释掉Text(entry.name),绑定本身的存在就会导致SwiftUI尝试读取已失效的对象。

  2. 通知更新存在时序差:虽然你监听了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 10:09:54