Ios 重新加载部分时出现表视图错误-似乎是某些竞争条件
我有一些很难解决的问题。它们经常发生在应用中,而不是以常规方式。我认为问题可能与一些比赛条件和同步有关 我正在使用这样的模式 1) 重新加载数据() 2) 重新加载节1()、重新加载节2()、重新加载节2()等 3) 我有一些tap事件可以像reloadData()一样重新加载 4) 还有一些Socket.IO消息可能会导致reloadData()、reloadSectionN() 5) 我尝试使用去抖动器只执行给定类型的最后一次重新加载 6) 如果请求-响应重新加载的持续时间比新socket.io事件到达的时间更长,则解Bouncer使用串行队列逐个串行执行任务 7) 节/表重载必须发生在UI线程上,因此在最后,我使用DispatchQueue.main.async{}移动到它 8) 我甚至尝试将数据过量/重新加载包装到信号量中,以阻止其他线程的修改 尽管如此,错误还是会发生,应用程序也会崩溃。我不知道是什么原因造成的 下面我将介绍代码中最重要的部分 我有这样的实例属性:Ios 重新加载部分时出现表视图错误-似乎是某些竞争条件,ios,multithreading,uitableview,semaphore,dispatch-queue,Ios,Multithreading,Uitableview,Semaphore,Dispatch Queue,我有一些很难解决的问题。它们经常发生在应用中,而不是以常规方式。我认为问题可能与一些比赛条件和同步有关 我正在使用这样的模式 1) 重新加载数据() 2) 重新加载节1()、重新加载节2()、重新加载节2()等 3) 我有一些tap事件可以像reloadData()一样重新加载 4) 还有一些Socket.IO消息可能会导致reloadData()、reloadSectionN() 5) 我尝试使用去抖动器只执行给定类型的最后一次重新加载 6) 如果请求-响应重新加载的持续时间比新socket.
private let debouncer = Debouncer()
private let debouncer1 = Debouncer()
private let debouncer2 = Debouncer()
private let serialQueue = DispatchQueue(label: "serialqueue")
private let semaphore = DispatchSemaphore(value: 1)
这里是Debouncer.debounce实例方法
func debounce(delay: DispatchTimeInterval, queue: DispatchQueue = .main, action: @escaping (() -> Void) ) -> () -> Void {
return { [weak self] in
guard let self = self else { return }
self.currentWorkItem?.cancel()
self.currentWorkItem = DispatchWorkItem {
action()
}
if let workItem = self.currentWorkItem {
queue.asyncAfter(deadline: .now() + delay, execute: workItem)
}
}
}
下面是表视图及其部分的取消公告重新加载
func debounceReload() {
let debounceReload = debouncer.debounce(delay: .milliseconds(500), queue: serialQueue) {
self.reloadData()
}
debounceReload()
}
func debounceReloadOrders() {
let debounceReload = debouncer1.debounce(delay: .milliseconds(500), queue: serialQueue) {
self.reloadOrdersSection(animating: false)
}
debounceReload()
}
可以在点击、拉动以刷新屏幕导航或Socket.IO事件(如果有多个用户,这里可能同时调用多个事件)时调用此解块方法
由debounce reload调用的每次重新加载都以这样的方法开始和结束(在这两种方法之间,对远程api的同步请求可能需要一些时间(并在此串行队列上执行)。所有Debouncer都重用相同的串行队列(因此,在重新加载tableview或其部分时,它们不应相互冲突/竞争,并导致数据不一致)
上面添加了此附加信号量作为附加检查,以确保数据一致性
func startLoading(section: Int, animating: Bool = true) {
self.semaphore.wait()
print("startLoading section \(section)")
tableView.beginUpdates()
if animating {
self.data[section] = .loadingSpinner
}
tableView.reloadSections([section], with: .none)
tableView.endUpdates()
self.semaphore.signal()
}
func stopLoading(section: Int, model: Model) {
self.semaphore.wait()
print("stopLoading section \(section)")
if section == 0 {
guard debouncer1.currentWorkItem?.isCancelled != true else { return }
} else if section == 1 {
guard debouncer2.currentWorkItem?.isCancelled != true else { return }
}
tableView.beginUpdates()
self.data[section] = model
tableView.reloadSections([section], with: .none)
tableView.endUpdates()
self.semaphore.signal()
}
private func clearData() {
self.semaphore.wait()
print("clearData")
data.removeAll()
self.semaphore.signal()
}
我认为不需要这个额外的信号量,因为这是使用串行队列,所以所有请求-响应重新加载都在串行队列上执行。可能有些问题是,我需要从串行队列切换到主队列,以便清除/添加微调器重新加载,然后填充数据/重新加载表或节。但我认为它应该比下一个更短数据替换发生了。我考虑使用Self.DATA =数据分配在这个关键部分中,将SoField.Delasy.AppEnter(Mule1)添加到StRoopAddit()的StPravaDebug()中的信号量临界区。
我遇到的示例错误:
致命异常:nsInternalInconsistenceException无效更新:
节0中的行数无效。中包含的行数无效
更新(3)后的现有节必须等于编号
更新(5)之前该节中包含的行数,加号或减号
从该节插入或删除的行数(插入0,
0)并加上或减去移入或移出的行数
我也看到过cellForRow函数的错误,这种错误一周发生几次,应用程序一天使用多次,所以很难重复这种错误。我曾尝试从邮递员那里发送简单的刷新套接字,但它们不会更改下划线数据(行数),我想一切正常
更新
我已将stopLoading()更新为stopLoading(data:),以便在主队列上更新数据源和tableView.reload。
因此,所有startLoading、stopLoading和reload方法都是在DispatchQueue.main.async{}上执行的
private func stopLoading(data: [Model]) {
guard debouncer.currentWorkItem?.isCancelled != true else { return }
self.semaphore.wait()
print("stopLoading")
self.data = data
tableView.reloadData()
activityIndicator.isHidden = true
refreshControl.endRefreshing()
tableView.isHidden = false
self.semaphore.signal()
}
您不需要任何这些信号量或去Bouncer。始终将更新发送到主队列上的表视图数据源数组。主队列是一个串行调度队列,这意味着您不能在从另一个更新重新加载表的同时更新数据源。但如果同时重新加载,请点击按钮(重新加载表)、socket.io节刷新等。然后在同一时间我获得重新加载数据、重新加载节1()等调用。然后有对api的请求,此请求响应的时间可能不同。它们甚至可以是多个重新加载节1()在短时间间隔内。reloadData发出多个请求,然后合并来自2或3个请求的数据以显示在tableview中。同时,在刷新时,我用加载微调器替换数据源,以在节中而不是真实数据中显示它。因此,我认为仅在主队列上执行reloadData()是不可能的启动然后隐藏表视图并显示微调器,同时重新加载section1()启动,用微调器替换section1,然后完成,然后重新加载日期获取section1数据(旧)之前,请与section2 data合并,更新数据源,然后重新显示刷新所有表视图。将出现更多不一致。解决线程问题的唯一方法是在单个串行调度队列中执行所有操作。由于UI更新必须在主队列上进行,因此这是一个明显的选择。因此,数据更新urce应该很快,所以我看不出在主队列上执行所有数据源和表更新有什么问题OK,但是尽管有这种去公告(都在同一个自定义串行队列中),这个代码startLoading(),stopLoading,startLoading(section:)stopLoading(section:)是在UI线程上执行的,比如_uiThread(startLoading),_uiThread(停止加载),等等。所以数据源的更新和重新加载被切换到UI主线程(DispatchQueue.Main.async{})
0x195bef098 +[_CFXNotificationTokenRegistration keyCallbacks]
3 Foundation 0x1966b2b68 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
4 UIKitCore 0x1c23ecc78 -[UITableView _endCellAnimationsWithContext:]
5 UIKitCore 0x1c24030c8 -[UITableView endUpdates]
6 MyApplication 0x104b39230 MyViewController.reload(section:) + 168
> that section (0 moved in, 0 moved out).
private func stopLoading(data: [Model]) {
guard debouncer.currentWorkItem?.isCancelled != true else { return }
self.semaphore.wait()
print("stopLoading")
self.data = data
tableView.reloadData()
activityIndicator.isHidden = true
refreshControl.endRefreshing()
tableView.isHidden = false
self.semaphore.signal()
}