ios table刷新问题
在公司做一个网盘的项目,包括ios端和mac端。其中传输列表是比较麻烦的事情,逻辑是这样的,文件的传输信息和请求任务放在数据源datalist里,请求任务nsurlsession回调传输情况更新传输进度并计算出速率等显示信息,文件传输成功后需要对相应的item进行删除并更新,问题出现在这里,举个例子,当下载item A刚好下载完成后删除该item并使用[table reloadData]这种方式刷新,此时下载item B也下载完成,删除item B,此时第一次刷新正在进行, 这样就会导致数据源错误的问题。
解决方案:
要做到传输一个就删除一个item,解决这个问题的思路应该满足两个条件,第一,能够监听到table何时刷新完成,第二,在进行刷新时对删除数据源操作进行加锁。
方法一
table函数laytoutIfNeeded会强制重绘并等待完成,可以在刷新代码后调用此函数,该函数是同步函数,在该操作执行完成后可以保证此时table已经刷新完毕。实践时无法保证layoutifneeded监听到table刷新完毕事件
[self.tableview reloadData]
[self.tableview layoutIfNeeded]
方法二
监听tableview的加载完成信息,tableview的layoutsubviews执行后便是tableview加载完成,我们可以这样操作,即在第一次刷新时添加信号量,第二次删除数据源时等待,在第一次刷新完成后释放信号量。这样操作可以达到上面提到的两种要求的效果。写了个demo展示该逻辑,逻辑与下面代码同:
func task(){
let semaphore = DispatchSemaphore.init(value: 0)
DispatchQueue.global().async {
sleep(5);
self.dataList.remove(at: self.dataList.count-1)
print("我是刷新1");
DispatchQueue.main.async {
self.tableView?.reloadDataWithCompletion {
print("加载1完成")
semaphore.signal()
}
}
}
DispatchQueue.global().async {
sleep(5)
usleep(useconds_t(0.002*1000000))
semaphore.wait()
self.dataList.remove(at: self.dataList.count-1)
print("我是刷新2");
DispatchQueue.main.async {
self.tableView?.reloadDataWithCompletion {
print("加载2完成")
}
}
}
}
两个global.async任务模拟传输任务,sleep模拟传输需要时间,第一次删除数据源刷新table,刷新完成后释放信号量,此时第二次刷新才能进入同样的流程。下面这个函数更能模拟这个流程:
func simulate_transList(){
for i in 1...10{
DispatchQueue.global().async {
if(self.lock==nil){
self.lock=DispatchSemaphore.init(value: 0)
}
else{
self.lock?.wait()
}
self.dataList.remove(at: self.dataList.count-1)
print("刷新\(i)开始 \(Utils.getDateTime())");
DispatchQueue.main.async {
self.tableView?.reloadDataWithCompletion {
print("刷新\(i)完成 \(Utils.getDateTime())")
self.lock?.signal()
}
}
}
}
}
第二种方式可以保证table reload的时候不会因为数据源的变化而造成错误,但是实际操作时可能会出现刷新顺序不是按照for循环的顺序执行的问题,猜测是由于几次操作同时进入刷新。
那这样我们可以整理下思路了,子类化tableview并实现重写layoutviews事件,定义一个全局信号量,在文件传输完成时进行wait操,tableview刷新完毕后进行signal操作,wait一次signal一次,这样能起到刷新时对正在刷新的数据源的加锁功能。
网友评论