简体   繁体   English

Swift中使用并发的函数/代码设计

[英]Function/Code Design with Concurrency in Swift

I'm trying to create my first app in Swift which involves making multiple requests to a website. 我正在尝试在Swift中创建我的第一个应用程序,其中涉及向网站发出多个请求。 These requests are each done using the block 这些请求均使用块完成

var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in ... } 
task.resume()

From what I understand this block uses a thread different to the main thread. 根据我的理解,这个块使用与主线程不同的线程。

My question is, what is the best way to design code that relies on the values in that block? 我的问题是,设计依赖于该块中的值的代码的最佳方法是什么? For instance, the ideal design (however not possible due to the fact that the thread executing these blocks is not the main thread) is 例如,理想的设计(但是由于执行这些块的线程不是主线程的事实而不可能)

func prepareEmails() {
    var names = getNames()
    var emails = getEmails()
    ...
    sendEmails()
}

func getNames() -> NSArray {
    var names = nil
    ....
    var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in 
        names = ...
    }) 
    task.resume()
    return names
}

func getEmails() -> NSArray {
    var emails = nil
    ....
    var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in 
        emails = ...
    }) 
    task.resume()
    return emails
}

However in the above design, most likely getNames() and getEmails() will return nil, as the the task will not have updated emails/name by the time it returns. 但是在上面的设计中,很可能getNames()和getEmails()将返回nil,因为任务在返回时不会更新电子邮件/名称。

The alternative design (which I currently implement) is by effectively removing the 'prepareEmails' function and doing everything sequentially in the task functions 替代设计(我目前正在实现)是通过有效地删除'prepareEmails'功能并在任务函数中按顺序执行所有操作

func prepareEmails() {
    getNames()
}

func getNames() {
    ...
    var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in 
        getEmails(names)
    }) 
    task.resume()
}

func getEmails(names: NSArray) {
    ...
    var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in 
        sendEmails(emails, names)
    }) 
    task.resume()
}

Is there a more effective design than the latter? 有没有比后者更有效的设计? This is my first experience with concurrency, so any advice would be greatly appreciated. 这是我第一次使用并发性,所以任何建议都会非常感激。

The typical pattern when calling an asynchronous method that has a completionHandler parameter is to use the completionHandler closure pattern, yourself. 调用具有completionHandler参数的异步方法时的典型模式是自己使用completionHandler闭包模式。 So the methods don't return anything, but rather call a closure with the returned information as a parameter: 所以这些方法不会return任何内容,而是使用返回的信息作为参数调用闭包:

func getNames(completionHandler:(NSArray!)->()) {
    ....
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {data, response, error -> Void in 
        let names = ...
        completionHandler(names)
    }
    task.resume()
}

func getEmails(completionHandler:(NSArray!)->()) {
    ....
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {data, response, error -> Void in 
        let emails = ...
        completionHandler(emails)
    }
    task.resume()
}

Then, if you need to perform these sequentially, as suggested by your code sample (ie if the retrieval of emails was dependent upon the names returned by getNames ), you could do something like: 然后,如果您需要按照代码示例的建议顺序执行这些操作(即,如果检索电子邮件取决于getNames返回的名称),您可以执行以下操作:

func prepareEmails() {
    getNames() { names in 
        getEmails() {emails in
            sendEmails(names, emails) // I'm assuming the names and emails are in the input to this method
        }
    }
}

Or, if they can run concurrently, then you should do so, as it will be faster. 或者,如果它们可以同时运行,那么你应该这样做,因为它会更快。 The trick is how to make a third task dependent upon two other asynchronous tasks. 诀窍是如何使第三个任务依赖于另外两个异步任务。 The two traditional alternatives include 两种传统的替代方案包括

  1. Wrapping each of these asynchronous tasks in its own asynchronous NSOperation , and then create a third task dependent upon those other two operations. 将这些异步任务中的每一个包装在自己的异步NSOperation ,然后根据其他两个操作创建第三个任务。 This is probably beyond the scope of the question, but you can refer to the Operation Queue section of the Concurrency Programming Guide or see the Asynchronous vs Synchronous Operations and Subclassing Notes sections of the NSOperation Class Reference . 这可能超出了问题的范围,但您可以参考“ 并发编程指南”的“ 操作队列”部分,或参阅NSOperation类参考的“ 异步与同步操作”和“ 子类注释”部分。

  2. Use dispatch groups, entering the group before each request, leaving the group within the completion handler of each request, and then adding a dispatch group notification block (called when all of the group "enter" calls are matched by their corresponding "leave" calls): 使用调度组,在每个请求之前输入组,将组留在每个请求的完成处理程序中,然后添加一个调度组通知块(当所有组“enter”调用与相应的“leave”调用匹配时调用):

     func prepareEmails() { let group = dispatch_group_create() var emails: NSArray! var names: NSArray! dispatch_group_enter(group) getNames() { results in names = results dispatch_group_leave(group) } dispatch_group_enter(group) getEmails() {results in emails = results dispatch_group_leave(group) } dispatch_group_notify(group, dispatch_get_main_queue()) { if names != nil && emails != nil { self.sendEmails(names, emails) } else { // one or both of those requests failed; tell the user } } } 

Frankly, if there's any way to retrieve both the emails and names in a single network request, that's going to be far more efficient. 坦率地说,如果有任何方法可以在单个网络请求中检索电子邮件和名称,那将会更有效率。 But if you're stuck with two separate requests, you could do something like the above. 但如果您遇到两个单独的请求,您可以执行类似上述操作。

Note, I wouldn't generally use NSArray in my Swift code, but rather use an array of String objects (eg [String] ). 注意,我通常不会在我的Swift代码中使用NSArray ,而是使用String对象数组(例如[String] )。 Furthermore, I'd put in error handling where I return the nature of the error if either of these fail. 此外,我将错误处理放在哪里,如果其中任何一个失败,我将返回错误的性质。 But hopefully this illustrates the concepts involved in (a) writing your own methods with completionHandler blocks; 但希望这说明了(a)用completionHandler块编写自己的方法所涉及的概念; and (b) invoking a third bit of code dependent upon the completion of two other asynchronous tasks. (b)根据两个其他异步任务的完成调用第三位代码。

The answers above (particularly Rob's DispatchQueue based answer) describe the concurrency concepts necessary to run two tasks in parallel and then respond to the result. 上面的答案(特别是基于Rob的DispatchQueue的答案)描述了并行运行两个任务然后响应结果所需的并发概念。 The answers lack error handling for clarity because traditionally, correct solutions to concurrency problems are quite verbose. 为了清晰起见,答案缺乏错误处理,因为传统上,并发问题的正确解决方案非常冗长。

Not so with HoneyBee . HoneyBee不是这样。

HoneyBee.start()
        .setErrorHandler(handleErrorFunc)
        .branch {
             $0.chain(getNames)
             +
             $0.chain(getEmails)
        }
        .chain(sendEmails)

This code snippet manages all of the concurrency, routes all errors to handleErrorFunc and looks like the concurrent pattern that is desired. 此代码段管理所有并发,将所有错误路由到handleErrorFunc并且看起来像所需的并发模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM