Swift 关闭异步执行顺序 [英] Swift closure async order of execution

查看:27
本文介绍了Swift 关闭异步执行顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In my model have function to fetch data which expects completion handler as parameter:

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

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

It's calling another function which does asynchronous loading of contacts, to which I'm forwarding my completion

fetchMostRecent 调用完成后如下所示:

The call of fetchMostRecent with completion looks like this:

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

这有时会奏效,但通常执行顺序并不像我期望的那样.问题是,有时 //2 下的 completion()//1 下的 if 作用域之前执行完成了.

This sometimes it works, but very often the order of execution is not the way as I would expect. Problem is, that sometimes completion() under // 2 is executed before scope of if under // 1 was finished.

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

Why is that? How can I ensure that execution of // 2 is started after // 1?

推荐答案

几点意见:

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

  1. It will always execute what's at 1 before 2. The only way you'd get the behavior you describe is if you're doing something else inside that for loop that is, itself, asynchronous. And if that were the case, you'd use a dispatch group to solve that (or refactor the code to handle the asynchronous pattern). But without seeing what's in that for loop, it's hard to comment further. The code in the question, alone, should not manifest the problem you describe. It's got to be something else.

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

Unrelated, you should note that it's a little dangerous to be updating model objects inside your asynchronously executing for loop (assuming it is running on a background thread). It's much safer to update a local variable, and then pass that back via the completion handler, and let the caller take care of dispatching both the model update and the UI updates to the main queue.

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

In comments, you mention that in the for loop you're doing something asynchronous, and something that must be completed before the completionHandler is called. So you'd use a dispatch group to do ensure this happens only after all the asynchronous tasks are done.

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

Note, since you're doing something asynchronous inside the for loop, not only do you need to use a dispatch group to trigger the completion of these asynchronous tasks, but you probably also need to create your own synchronization queue (you shouldn't be mutating an array from multiple threads). So, you might create a queue for this.

综合起来,你会得到类似的结果:

Pulling this all together, you end up with something like:

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 中:

Or, in 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)
        }
    }
}

这篇关于Swift 关闭异步执行顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆