【发布时间】:2015-08-28 19:09:33
【问题描述】:
代码
var animals: Results<Animal> {
didSet {
self.tableView.reloadData()
}
}
问题
- 我有一个
UITableView显示动物列表 - 我还有一个后台进程定期同步动物列表
- 显示 UITableView 的 UIViewController 通过成员变量跟踪动物。
- 如果在我查看列表时后台进程从 Realm DB 中删除了最后一个 Animal,
animals集合会更新,但如果我滚动到哪里,UITableView不知道它丢失了一行删除的 Animal 本来是,我从 Realm 收到一个 Index of bounds 异常并且应用程序崩溃了。
尝试的解决方案
- 我尝试将
Results<Animal>复制到内存中的[Animal]并将UITableView绑定到那个,因为Array不会像Results<Animal>那样自动刷新(没有办法转对吧?)。这绕过了索引越界异常,除了现在我收到一个访问已删除/无效对象上的属性的异常,因为当单元格读取已删除对象上的属性时,它必须返回到领域。不会工作。 - 然后我尝试收听 Realm 的通知和
.reloadData相应的表。这可行,但会导致过多的表重新加载,并增加确保通知令牌被释放等的开销,并记住在应用程序中的所有表视图中执行此操作。
解决方法
我最终创建了一个 RealmTableHandler 单例,它基本上是解决方案 #2 的升级版。
public extension UITableView {
public func addToRealmTableHandler() {
RealmTableHandler.sharedInstance.addTrackedTable(self)
}
}
/// Listens for Realm Notifications and reloads tracked UITableViews if needed
public class RealmTableHandler {
public static let sharedInstance = RealmTableHandler()
private var notificationToken: NotificationToken!
private var tableIdentifier = 0
private lazy var trackedTables = NSMapTable(keyOptions: NSHashTableWeakMemory, valueOptions: NSHashTableWeakMemory)
private lazy var trackedDelegates = NSMapTable(keyOptions: NSHashTableWeakMemory, valueOptions: NSHashTableWeakMemory)
private lazy var trackedDatasources = NSMapTable(keyOptions: NSHashTableWeakMemory, valueOptions: NSHashTableWeakMemory)
private init() {
self.startListening()
}
private func startListening() {
self.notificationToken = Realm().addNotificationBlock { (notification, realm) -> Void in
self.checkTrackedTables()
}
}
public func addTrackedTable(table: UITableView) {
if self.trackedTables.count == 0 {
self.tableIdentifier = 0
}
if let delegate = table.delegate, datasource = table.dataSource {
self.tableIdentifier++
self.trackedTables.setObject(table, forKey: self.tableIdentifier)
self.trackedDelegates.setObject(delegate, forKey: self.tableIdentifier)
self.trackedDatasources.setObject(datasource, forKey: self.tableIdentifier)
}
}
private func checkTrackedTables() {
// Ensure table, delegate, datasource are all available, sometimes the delegate or datasource are deallocated before the table, but the table keeps a reference to them
for key in self.trackedTables.keyEnumerator().allObjects {
if let table = self.trackedTables.objectForKey(key) as? UITableView,
delegate = self.trackedDelegates.objectForKey(key) as? UITableViewDelegate,
datasource = self.trackedDatasources.objectForKey(key) as? UITableViewDataSource
{
// Check if tracked tables need to be reloaded due to Realm data changes
let numSectionsInTableView = table.numberOfSections()
var numRowsInTableView = 0
let numSectionsInDatasource = datasource.numberOfSectionsInTableView?(table) ?? numSectionsInTableView
var numRowsInDatasource = 0
if numSectionsInTableView <= 0 || numSectionsInDatasource <= 0 {
return
}
for i in 0...(numSectionsInTableView - 1) {
numRowsInTableView += table.numberOfRowsInSection(i)
}
for i in 0...(numSectionsInDatasource - 1) {
numRowsInDatasource += datasource.tableView(table, numberOfRowsInSection: i)
}
if numRowsInTableView != numRowsInDatasource {
table.reloadData()
}
}
}
}
}
然后我将以下代码添加到任何UITableViews 我想在 Realm 增加或减少其行数时重新加载。
self.tableView.addToRealmTableHandler()
这似乎工作正常,即使使用分段 UITableViews(只要分段数据也是 Results<T>)。
有没有更好/更简单的方法来解决这个问题?
Realm 将来会支持表插入/删除吗?
【问题讨论】:
-
您可以使用 reloadRowsAtIndexPaths、addRowsAtIndexPaths、removeRowsAtIndexPaths、deleteRowsAtIndexPaths 添加、删除、移动或重新加载单行,而不是 ReloadData。
-
是的。不过,我想要一个全球通用的解决方案。它不知道哪些行索引受到影响。