Swift 迅捷+;领域:从列表中删除一行会导致异常';RLMEException';,原因:';对象已被删除或无效;

Swift 迅捷+;领域:从列表中删除一行会导致异常';RLMEException';,原因:';对象已被删除或无效;,swift,realm,swiftui,Swift,Realm,Swiftui,我正在尝试将一个小的RSS提要阅读器应用程序从UIKit移植到SwiftUI,这个应用程序使用领域进行持久化 为了使领域在SwiftUI中可绑定,我在项目中添加了以下代码: import Foundation import SwiftUI import RealmSwift final class FeedData: ObservableObject { @Published var feeds: [Feed] { didSet { clea

我正在尝试将一个小的RSS提要阅读器应用程序从UIKit移植到SwiftUI,这个应用程序使用领域进行持久化

为了使领域在SwiftUI中可绑定,我在项目中添加了以下代码:

import Foundation
import SwiftUI
import RealmSwift


final class FeedData: ObservableObject {

    @Published var feeds: [Feed] {
        didSet {
            cleanRealm()
        }
    }
    private var feedsToken: NotificationToken?

    private func activateFeedsToken() {
        let realm = try! Realm()
        let feeds = realm.objects(Feed.self)
        feedsToken = feeds.observe { _ in
            self.feeds = Array(feeds)
        }
    }

    func cleanRealm() {
        let realm = try! Realm()
        let tempFeeds = realm.objects(Feed.self)
        let diff = feeds.difference(from: tempFeeds)
        for change in diff {
            switch change {
            case .remove(_, let element, _):
                do {
                    try realm.write {
                        print("Removing \(element.name)")
                        if element.isInvalidated {
                            print("Error: element invalidated.")
                        } else {
                            realm.delete(element)
                            print("Removed \(element.name)")
                        }
                    }
                } catch {
                    fatalError(error.localizedDescription)
                }
            default:
                break
            }
        }
    }

    init() {
        let realm = try! Realm()
        feeds = Array(realm.objects(Feed.self))
        activateFeedsToken()
    }

    deinit {
        feedsToken?.invalidate()
    }
}
因此,我们有一个Feed数组,其中有一个DidSet观察器,它在触发时调用cleanRealm()函数,然后使用集合扩散来删除不再在数组中但仍存储在Realm中的Feed对象-我知道这非常笨重,但我想它至少可以使数组与Realm数据库保持同步

在我的SwiftUI视图中,我使用FeedData作为EnvironmentObject,并使用一个列表显示使用ForEach的所有Feed对象

当用户从列表中删除提要时,它将从数组中删除,如下所示:

            .onDelete(perform: deleteItems)
然后调用此函数:

func deleteItems(at offsets: IndexSet) {
    print("Removing feeds at requested offsets.")
    feedData.feeds.remove(atOffsets: offsets)
    print("Removed feeds at requested offsets.")
}
问题:当我运行应用程序,然后从列表中删除条目时,会引发以下异常:

*由于未捕获的异常“RLMEException”而终止应用程序,原因是:“对象已被删除或无效。” *第一次抛出调用堆栈: (0x1a4a97ab0 0x1a47b1028 0x10098f6a8 0x100996324 0x1009962e8 0x10000dfa4 0x1b215d830 0x1b215d3f0 0x1b215d170 0x1b215f470 0x1DB363C 0x1DB363C 0x1DB36A68 0x1b22d647c 0x1BB3C4 0x1B22C4 0x1b21a924c 0x1b21a943c 0x1BA9B50 0x1BD6247C 0x1DB363C 0x1daefcd50 0x1DB368AC4 0x1DB366A6C04 0x1DB368C40 0x1DB4B4C40 0x1DB4B4C40 0xB15C40x1db1b836c 0x1db1bef70 0x1cf72c9c0 0x1cf713e9c 0x1cf714164 0x1cf719130 0x1db07f9f0 0x1db084e10 0x1db3ac770 0x1db07f96c 0x1cf719284 0x1db081ca0 0x1DB081D081B0818A48 0x1DB0814AC770 0x1db0817fc 0x1db09f848 0x1DAF000A10 0x1daf00970 0x1daf00a8c 1A4A12668 0x1AB088EA04088EA088) libc++abi.dylib:以NSException类型的未捕获异常终止

到目前为止,我的研究表明,当一个领域对象被删除时,它会在被完全删除之前发生变异

因此,我认为这里可能发生的情况是,当对象在从领域中移除之前发生变异时,SwiftUI会检测到这种变化,重新绘制视图,然后尝试访问现在无效的领域对象,从而引发异常

Realm对象确实有一个isInvalidated属性,在将其添加到列表之前,我可能应该检查该属性,但是AFAIK(请随时纠正我的错误)在ForEach块中无法检查此类条件并在需要时“继续”到下一个数组项

非常感谢您对此提供的任何帮助,我整天都在处理这个问题,只是找不到一个好的解决方案,这可能很明显,但我仍然在学习SwiftUI在处理这个示例项目时可以做的所有精彩的事情

谢谢

更新:

按照Jay的建议,我设法修改了FeedData类,以便它公开一个Realm Results属性供我的应用程序使用,而不是复制到单独的数组中

这也意味着我不再需要在删除提要时进行任何集合差异化,但是由于SwiftUI的.onDelete(perform:)修饰符需要一个接受索引集的函数,所以我的delete函数仍然有点麻烦

更新后的FeedData类现在如下所示:

import Foundation
import SwiftUI
import RealmSwift


final class FeedData: ObservableObject {

    @Published var feeds: Results<Feed>
    private var feedsToken: NotificationToken?

    private func activateFeedsToken() {
        let realm = try! Realm()
        let feeds = realm.objects(Feed.self)
        feedsToken = feeds.observe { _ in
            self.feeds = feeds
        }
    }

    func deleteItems(at offsets: IndexSet) {
        print("deleteItems called.")
        let realm = try! Realm()

        do {
            try realm.write {
                offsets.forEach { index in
                    print("Attempting to access index \(index).")
                    if index < feeds.count {
                        print("Index is valid.")
                        let item = feeds[index]
                        print("Removing \(item.name)")
                        realm.delete(item)
                        print("Removed item.")
                    }
                }
            }
        } catch {
            fatalError(error.localizedDescription)
        }
    }
    init() {
        let realm = try! Realm()
        feeds = realm.objects(Feed.self)
        activateFeedsToken()
    }

    deinit {
        feedsToken?.invalidate()
    }
}
<代码>导入基础 导入快捷键 导入RealmSwift 最终类FeedData:ObservieObject{ @已发布的var提要:结果 私人var feedsToken:NotificationToken? 私有函数activateFeedsToken(){ 让realm=try!realm() 让Feed=realm.objects(Feed.self) feedsToken=feeds.observe{uu}in self.feed=feed } } func deleteItems(偏移量处:IndexSet){ 打印(“删除已调用的项”) 让realm=try!realm() 做{ 试试realm.write{ offsets.forEach{中的索引 打印(“尝试访问索引\(索引)。”) 如果索引 虽然这在技术上是可行的,但我现在的问题是,一旦从领域中删除对象(通过使用断点进行测试),就会调用视图的ForEach()方法,这种情况甚至会在领域通知发出之前发生

这将导致尝试访问已删除对象的索引,在这种情况下,应用程序将崩溃,并出现来自领域的索引越界错误

不过,这应该是一个单独的问题,因为我原来的问题已经解决了

@杰伊,你能把你的第一条评论作为对我问题的回答,这样我就可以批准了吗


谢谢你的帮助

我也遇到了这个问题。我不知道发生了什么,我想是SwiftUI导致了一些复杂的多重调用行为。当我展开notificationToken观察块并打开更改以在调用删除时返回时,它工作正常。就你而言:

    feedsToken = feeds.observe { [weak self] (changes: RealmCollectionChange) in
        switch changes {
        case .initial:
            print("Initial call")
        case .update(_, let deletions, _, _):
            print("Updates made!")
            if !deletions.isEmpty { return }
            self.feeds = feeds
        case .error(let error):
            print("Error observing changes")
        }
    }

如果不深入研究代码,我会考虑另一种方法。领域对象是活动对象-当从领域中删除对象时,任何知道该对象的结果也会删除该对象。通过观察这些结果,应用程序会收到删除通知,并相应地更新UI。例如,通过扩散来移除不再在阵列中的馈送对象将不存在