【问题标题】:Realm observe with UICollectionView - race conditions使用 UICollectionView 观察领域 - 竞争条件
【发布时间】:2018-07-30 16:32:21
【问题描述】:

我使用Realm 作为缓存层,这样每当数据呈现给用户时,它首先从数据库中获取并显示给用户。随后,发送服务器请求以获取最新版本的数据,将其与Realm 数据库同步并在UICollectionView 中显示更改。

问题在于,当从Realm 数据库检索缓存数据并且UICollectionView 正在更新时,服务器更新请求有可能在UICollectionView 加载所有单元格之前完成,并且由于Results 列表是数据的实时集合,它可以被修改。现在,例如,如果在服务器端删除了一个项目,则实时集合将少持有一个项目,因此会导致超出范围的异常。

话虽如此,即使Realm 官方文档中提供的代码也不是线程安全的,考虑到results 可以更改而UITableView 逐行请求每一行:

class ViewController: UITableViewController {
    var notificationToken: NotificationToken? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        let realm = try! Realm()
        let results = realm.objects(Person.self).filter("age > 5")

        // Observe Results Notifications
        notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
            guard let tableView = self?.tableView else { return }
            switch changes {
            case .initial:
                // Results are now populated and can be accessed without blocking the UI
                tableView.reloadData()
            case .update(_, let deletions, let insertions, let modifications):
                // Query results have changed, so apply them to the UITableView
                tableView.beginUpdates()
                tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
                                     with: .automatic)
                tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
                                     with: .automatic)
                tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
                                     with: .automatic)
                tableView.endUpdates()
            case .error(let error):
                // An error occurred while opening the Realm file on the background worker thread
                fatalError("\(error)")
            }
        }
    }

    deinit {
        notificationToken?.invalidate()
    }
}

我能想到解决此问题的唯一方法是创建结果的深层副本,并使用 Semaphore 或类似方法同步观察函数的主体,以确保数据不会处于不一致状态,我考虑非常低效。 (注意tableView.endUpdates() 并不意味着UITableView 已经重新加载了所有数据,但它只是被分派到队列并准备异步处理。)

我想听听任何建议如何以有效的方式实现这一点,从而消除上述竞争条件。

【问题讨论】:

    标签: ios swift uicollectionview realm


    【解决方案1】:

    您需要在主线程上进行所有 UI 更新。如果您这样做,第一组结果会更新主线程上的集合视图,当下一组结果也出现时,它将在主线程上排队,以便在第一组完成后更新。

    【讨论】:

    • 实际的 UICollectionView 更新将连续完成是正确的,因为它们在同一个线程上,但是实际的数据收集可以在第一次更新期间更改,因为它是从后台线程修改的.
    【解决方案2】:

    基于:

    问题在于,当从 Realm 数据库中检索缓存数据并且 UICollectionView 正在更新时,服务器更新请求有可能在 UICollectionView 加载所有单元格之前完成,并且因为结果列表是实时的收集数据,它可能已经被修改了。

    我认为这不会发生,因为一旦您的实时收藏发生更改,就会触发更新通知,并且收藏将相应地重建/更新。但是,正如我在 PM 中所说的那样,我在不久前使用领域。

    测试你的假设很容易:降低模拟器的互联网速度,或者制作巨大的桌子等等。我真的很好奇你是否真的可以创造一个你认为你会遇到的问题。

    【讨论】:

    • 我实际上发生了 1/10 次,因为我让服务器在本地运行。
    • 嗯,在它发生之后(收藏视图正在加载,实时收藏已更改)接下来会发生什么?集合视图不会触发更新以更新其值吗?
    • 观察者将触发另一个 UICollectionView 更新(更新切换案例),该更新被分派到主线程上的队列(所以这里没有问题)。然而,初始更新尚未完成,因为集合视图正在逐个请求每个单元格,但例如从数据中删除了一项,因此导致索引超出范围。
    • 在您的集合视图中,您实现了 numberOfItems(inSection: Int) -> Int,对吗?你把它和你的现场收藏联系起来了,对吧?你可以分享你的 UICollectionView 实现吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-20
    • 1970-01-01
    • 2011-12-12
    • 2019-02-10
    • 2015-04-28
    • 1970-01-01
    相关资源
    最近更新 更多