美文网首页
多线程操作数据源引起的tableview刷新问题

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

作者: 辣条少年J | 来源:发表于2019-10-15 22:18 被阅读0次

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/bdzxmctx.html