一尘不染

Swift关闭异步执行顺序

swift

在我的模型中,具有获取数据的功能,该数据需要完成处理程序作为参数:

func fetchMostRecent(completion: (sortedSections: [TableItem]) -> ()) {

        self.addressBook.loadContacts({
            (contacts: [APContact]?, error: NSError?) in
            // 1
            if let unwrappedContacts = contacts {
                for contact in unwrappedContacts {

                    // handle constacts
                    ...                        
                    self.mostRecent.append(...)
                }
            }
            // 2
            completion(sortedSections: self.mostRecent)
        })
}

它正在调用另一个函数,该函数执行联系人的异步加载,我将完成情况转发到该函数

fetchMostRecent具有完成的调用如下所示:

model.fetchMostRecent({(sortedSections: [TableItem]) in
    dispatch_async(dispatch_get_main_queue()) {
        // update some UI
        self.state = State.Loaded(sortedSections)
        self.tableView.reloadData()
    }
})

有时这是可行的,但是执行的顺序常常不是我期望的那样。问题是,有时completion()// 2的范围之前执行if// 1结束了。

这是为什么?如何确保// 2在之后开始执行// 1


阅读 461

收藏
2020-07-07

共1个答案

一尘不染

一些观察:

  1. 它总是执行2之前1处的值。获得描述的行为的唯一方法是,如果要在for循环内执行其他操作,而该操作本身就是异步的。如果是这种情况,您将使用一个调度组来解决该问题(或重构代码以处理异步模式)。但是,如果看不到for循环中的内容,则很难进一步评论。仅问题中的代码不应显示您所描述的问题。一定是别的东西。

  2. 无关的,您应该注意,在异步执行的for循环中更新模型对象(假设它正在后台线程上运行)是有点危险的。更新局部变量,然后通过完成处理程序将其传递回去,让调用者负责将模型更新和UI更新都分派到主队列,这要安全得多。

  3. 在注释中,您提到在for循环中您正在执行异步操作,并且必须在调用completionHandler之前完成某些操作。因此,您将使用调度组来确保仅在完成所有异步任务之后才发生这种情况。

  4. 请注意,由于您在for循环内执行异步操作,因此不仅需要使用调度组来触发这些异步任务的完成,而且可能还需要创建自己的同步队列(您不应该进行突变来自多个线程的数组)。因此,您可以为此创建一个队列。

综合所有这些,您最终会得到以下结果:

func fetchMostRecent(completionHandler: ([TableItem]?) -> ()) {
    addressBook.loadContacts { contacts, error in
        var sections = [TableItem]()
        let group = dispatch_group_create()
        let syncQueue = dispatch_queue_create("com.domain.app.sections", nil)

        if let unwrappedContacts = contacts {
            for contact in unwrappedContacts {
                dispatch_group_enter(group)
                self.someAsynchronousMethod {
                    // handle contacts
                    dispatch_async(syncQueue) {
                        let something = ...
                        sections.append(something)
                        dispatch_group_leave(group)
                    }
                }
            }
            dispatch_group_notify(group, dispatch_get_main_queue()) {
                self.mostRecent = sections
                completionHandler(sections)
            }
        } else {
            completionHandler(nil)
        }
    }
}

model.fetchMostRecent { sortedSections in
    guard let sortedSections = sortedSections else {
        // handle failure however appropriate for your app
        return
    }

    // update some UI
    self.state = State.Loaded(sortedSections)
    self.tableView.reloadData()
}

或者,在Swift 3中:

func fetchMostRecent(completionHandler: @escaping ([TableItem]?) -> ()) {
    addressBook.loadContacts { contacts, error in
        var sections = [TableItem]()
        let group = DispatchGroup()
        let syncQueue = DispatchQueue(label: "com.domain.app.sections")

        if let unwrappedContacts = contacts {
            for contact in unwrappedContacts {
                group.enter()
                self.someAsynchronousMethod {
                    // handle contacts
                    syncQueue.async {
                        let something = ...
                        sections.append(something)
                        group.leave()
                    }
                }
            }
            group.notify(queue: .main) {
                self.mostRecent = sections
                completionHandler(sections)
            }
        } else {
            completionHandler(nil)
        }
    }
}
2020-07-07