多线程操作数据源引起的tableview刷新问题

2019-10-15  本文已影响0人  辣条少年J

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一次,这样能起到刷新时对正在刷新的数据源的加锁功能。

上一篇 下一篇

猜你喜欢

热点阅读