在我的模型中,具有获取数据的功能,该数据需要完成处理程序作为参数:
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具有完成的调用如下所示:
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结束了。
completion()
// 2
if
// 1
这是为什么?如何确保// 2在之后开始执行// 1?
一些观察:
它总是执行2之前1处的值。获得描述的行为的唯一方法是,如果要在for循环内执行其他操作,而该操作本身就是异步的。如果是这种情况,您将使用一个调度组来解决该问题(或重构代码以处理异步模式)。但是,如果看不到for循环中的内容,则很难进一步评论。仅问题中的代码不应显示您所描述的问题。一定是别的东西。
无关的,您应该注意,在异步执行的for循环中更新模型对象(假设它正在后台线程上运行)是有点危险的。更新局部变量,然后通过完成处理程序将其传递回去,让调用者负责将模型更新和UI更新都分派到主队列,这要安全得多。
在注释中,您提到在for循环中您正在执行异步操作,并且必须在调用completionHandler之前完成某些操作。因此,您将使用调度组来确保仅在完成所有异步任务之后才发生这种情况。
for
请注意,由于您在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) } } }