Swift关闭异步执行顺序

时间:2016-10-05 17:42:50

标签: swift asynchronous closures

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

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之后启动#{bean.property eq 'a' ? 'Ant' : bean.property eq 'b' ? 'Bob' : bean.property eq 'c' ? 'C++' : null} 的执行?

1 个答案:

答案 0 :(得分:3)

有几点意见:

  1. 它总会在2之前执行1的处理。你获得所描述的行为的唯一方法就是你在for循环中做的其他事情本身是异步的。如果是这种情况,您将使用调度组来解决(或重构代码以处理异步模式)。但是,如果没有看到循环中的内容,就很难进一步评论。单独的问题中的代码不应该表明您描述的问题。它必须是别的东西。

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

  3. 在评论中,你提到在for循环中你正在做异步的事情,并且必须在调用completionHandler之前完成。因此,您需要使用调度组来确保仅在完成所有异步任务后才会发生这种情况。

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

  5. 将这些全部拉到一起,你最终得到的结果是:

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