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

查看:221
本文介绍了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天全站免登陆