Realm数据源删除全部数据时应用崩溃的解决方案咨询
SwiftUI + Realm 删除全部数据时崩溃:Object has been deleted or invalidated
问题场景
在使用Realm作为数据源的SwiftUI应用中,点击“Remove all data”按钮删除指定类型全部数据时,应用偶尔抛出Object has been deleted or invalidated异常。目前临时解决方法(注释ForEach循环或添加延迟)不够优雅,需要根本解决方案。
问题根源
- 你将Realm的
Object实例直接存储在@Published修饰的data数组中,这些实例是绑定到Realm数据库的“活对象”,其有效性完全依赖于数据库中的状态。 - 调用
realm.delete(allObjects)时,数据库内的对象会被立即标记为已删除,但SwiftUI视图可能还未完成data = []的更新渲染,此时视图仍在尝试访问已失效的Realm对象,最终触发崩溃。 - 临时延迟方案只是通过等待视图更新完成再执行删除操作来规避竞态条件,并非从根源解决问题。
解决方案
方案1:直接绑定Realm的Results(推荐)
Realm的Results是自带自动更新能力的集合类型,天然支持SwiftUI的响应式机制,无需手动维护@Published数组,视图会自动同步数据库状态,彻底避免失效对象访问问题。
修改RealmManager:
import Foundation import RealmSwift @MainActor final class RealmManager: ObservableObject { let realm: Realm? // 直接暴露Realm Results,无需@Published(Results本身是Observable) var items: Results<Item>? { realm?.objects(Item.self) } init() { guard let url = Bundle.main.url(forResource: "seed", withExtension: "realm") else { fatalError("Could not find seed") } realm = try! Realm(configuration: Realm.Configuration(schemaVersion: 4, seedFilePath: url)) } func deleteAll() { guard let realm, let items = items else { return } do { try realm.write { print("Deleting \(items.count) objects") realm.delete(items) } } catch { print("deleteAll(): \(error)") } } }
修改视图代码:
struct NameView: View { @StateObject private var realmManager = RealmManager() var body: some View { NavigationStack { List { // 直接遍历Realm Results,ForEach原生支持该类型 if let items = realmManager.items { ForEach(items) { item in NavigationLink(destination: EmptyView()) { Text(item.name) } } } } Button("Remove all data") { realmManager.deleteAll() } } } }
方案2:使用不可变DTO隔离Realm对象
如果需要维护本地数据数组,不要直接存储Realm的Object实例,而是将数据复制到独立的DTO(数据传输对象)结构体中,彻底隔离视图与Realm数据库的绑定关系。
首先定义DTO结构体:
// 独立于Realm的DTO,实现Identifiable供ForEach使用 struct ItemDTO: Identifiable { let id: UUID let name: String // 从Realm Item实例转换为DTO init(from item: Item) { self.id = item.id self.name = item.name } }
修改RealmManager:
import Foundation import RealmSwift @MainActor final class RealmManager: ObservableObject { @Published private(set) var data: [ItemDTO] = [] let realm: Realm? private var notificationToken: NotificationToken? // 监听Realm数据变化自动更新DTO init() { guard let url = Bundle.main.url(forResource: "seed", withExtension: "realm") else { fatalError("Could not find seed") } realm = try! Realm(configuration: Realm.Configuration(schemaVersion: 4, seedFilePath: url)) setupRealmObservation() } private func setupRealmObservation() { guard let realm else { return } let allItems = realm.objects(Item.self) // 监听Realm数据变化,自动同步DTO数组 notificationToken = allItems.observe { [weak self] changes in guard let self = self else { return } switch changes { case .initial(let items): self.data = items.map(ItemDTO.init) case .update(let items, _, _, _): self.data = items.map(ItemDTO.init) case .error(let error): print("Realm observation error: \(error)") } } } func deleteAll() { guard let realm else { return } let allItems = realm.objects(Item.self) do { try realm.write { print("Deleting \(allItems.count) objects") realm.delete(allItems) } } catch { print("deleteAll(): \(error)") } } deinit { notificationToken?.invalidate() // 销毁时取消监听,避免内存泄漏 } }
视图代码无需修改,ForEach依然遍历realmManager.data,但此时访问的是独立的DTO结构体,不会受到Realm对象删除的影响。
方案说明
- 方案1利用Realm原生的响应式能力,
Results会在数据库变化时即时通知视图更新,确保视图永远访问有效数据,代码最简洁高效。 - 方案2通过DTO实现视图与数据库的解耦,适合需要对数据进行本地加工、转换的场景,同时保证数据同步。
内容的提问来源于stack exchange,提问作者irrbloss
相关产品推荐
相关产品推荐

