Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 重新加载部分时出现表视图错误-似乎是某些竞争条件_Ios_Multithreading_Uitableview_Semaphore_Dispatch Queue - Fatal编程技术网

Ios 重新加载部分时出现表视图错误-似乎是某些竞争条件

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.

我有一些很难解决的问题。它们经常发生在应用中,而不是以常规方式。我认为问题可能与一些比赛条件和同步有关

我正在使用这样的模式

1) 重新加载数据()

2) 重新加载节1()、重新加载节2()、重新加载节2()等

3) 我有一些tap事件可以像reloadData()一样重新加载

4) 还有一些Socket.IO消息可能会导致reloadData()、reloadSectionN()

5) 我尝试使用去抖动器只执行给定类型的最后一次重新加载

6) 如果请求-响应重新加载的持续时间比新socket.io事件到达的时间更长,则解Bouncer使用串行队列逐个串行执行任务

7) 节/表重载必须发生在UI线程上,因此在最后,我使用DispatchQueue.main.async{}移动到它

8) 我甚至尝试将数据过量/重新加载包装到信号量中,以阻止其他线程的修改

尽管如此,错误还是会发生,应用程序也会崩溃。我不知道是什么原因造成的

下面我将介绍代码中最重要的部分

我有这样的实例属性:

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()
    }