我正试图将一个小RSS RSS feed阅读器应用程序从UIKit移植到SwiftUI,该应用程序使用Realm进行持久化。
为了使Realm在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()
}
}
因此,我们有一个带有DidSet观察器的feed数组,该观察器在被触发时调用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.")
}
问题:当我运行我的应用程序,然后从列表中删除一个条目时,会引发以下异常:
*由于未捕获的异常“ RLMException”而终止应用程序,原因:“对象已被删除或无效。” * 第一个调用堆栈: (0x1a4a97ab0 0x1a47b1028 0x10098f6a8 0x100996324 0x1009962e8 0x10000dfa4 0x1b215d830 0x1b215d3f0 0x1b215d170 0x1b215f470 0x1db363c4c 0x1db36a0c8 0x1db36a240 0x1db36a6c0 0x1b22d647c 0x1db3ca2c4 0x1db3c9f04 0x1b21a924c 0x1b21a943c 0x1b21a9b50 0x1b22d647c 0x1daefcd50 0x1db3f0ac4 0x1db3eb7b8 0x1db3ead20 0x1db14dca4 0x1db14c5c0 0x1db4d5d0c 0x1db1bdc1c 0x1db1b836c 0x1db1bef70 0x1cf72c9c0 0x1cf713e9c 0x1cf714164 0x1cf719130 0x1db07f9f0 0x1db084e10 0x1db3ac770 0x1db07f96c 0x1cf719284 0x1db081ca0 0x1db081a48 0x1db0816c8 0x1db081834 0x1db3ac770 0x1db0817fc 0x1db09f848 0x1daf00a10 0x1daf00970 0x1daf00a8c 0x1a4a12668 0x1a4a0d308 0x1a4a0d8b8 0x1a4a0d084 0x1aec56534 0x1a8b7b8b8 0x10004e520 0x1a488ce18) libc ++ abi.dylib:以类型为NSException的未捕获异常终止
到目前为止,我的研究表明,删除Realm对象后,该对象会先被突变,然后再完全删除。
所以,我认为这里可能发生的事情是,当对象在从Realm中删除之前发生了突变时,SwiftUI会检测到此更改,重新绘制视图,然后尝试访问现已失效的Realm对象,从而引发异常
领域对象确实具有isInvalidated属性,我可能应该在将其添加到列表之前对其进行检查,但是AFAIK(并且请随时对此进行更正)在ForEach块中无法检查这种情况并根据需要“继续”到下一个数组项。
对此的任何帮助都将不胜感激,我整日忙于解决此问题,只是找不到一个好的解决方案,这很明显,但我仍在学习SwiftUI在进行工作时可以做的所有精彩工作这个示例项目。
谢谢!
更新:
按照Jay的建议,我设法修改了FeedData类,以便它为我的应用程序提供Realm Results属性,而不是复制到单独的数组中。
这也意味着删除Feed时,我不再需要进行任何集合区分,但是由于SwiftUI的.onDelete(perform :)修饰符期望一个可以接受IndexSet的函数,所以我的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()
}
}
尽管这在技术上是可行的,但现在的问题是,一旦从Realm中删除对象(通过使用断点进行测试),就会立即调用视图的ForEach()方法,甚至在分派Realm通知之前也会发生这种情况。 / p>
这将导致尝试访问已删除对象的索引,在这种情况下,应用程序将崩溃,并出现Realm的索引超出范围错误。
这可能应该是一个单独的问题,因为我原来的问题已经解决。
@Jay,能否请您将第一个评论标记为我的问题的答案,以便我批准?
感谢您的大力帮助!
答案 0 :(得分:0)
我也遇到了这个问题。我想不确定发生了什么,由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")
}
}