Multithreading 在返回值Swift的函数中进行多线程处理的最佳实践

Multithreading 在返回值Swift的函数中进行多线程处理的最佳实践,multithreading,swift,design-patterns,architectural-patterns,Multithreading,Swift,Design Patterns,Architectural Patterns,我有一个问题可能不是关于实现的,而是关于技巧/最佳实践的问题 我正在使用Swift开发一个类,该类以JSON格式从在线源获取数据。我想在这个类中有一些特定的方法,这些方法连接到在线源代码,并在字典类型中返回结果。函数可能如下所示: func getListFromOnline()->[String:String]{ var resultList : [String:String]=[:] ... /* Some HTTP request is sent t

我有一个问题可能不是关于实现的,而是关于技巧/最佳实践的问题

我正在使用Swift开发一个类,该类以JSON格式从在线源获取数据。我想在这个类中有一些特定的方法,这些方法连接到在线源代码,并在字典类型中返回结果。函数可能如下所示:

func getListFromOnline()->[String:String]{

    var resultList : [String:String]=[:]
    ...
    /*
    Some HTTP request is sent to the online source with NSURLRequest

    Then I parse the result and assign it to the resultList
    */
    ...

    return resultList
}
func asynchStuff(completionHandler: ([String:String]) -> Void) {
   // do asynchronous stuff, building a [String:String]
   let result: [String: String] = // the result we got async
   completionHandler(result)
}
我现在在这个实现中得到的是,在没有多线程的情况下,
resultList
显然是在从在线源获取之前返回的。毫不奇怪,它会导致程序失败

有什么想法或技巧可以让我达到相反的效果吗?在这个例子中,我对多线程有点困惑,因为我想稍后从另一个类异步调用这个公共方法,我知道如何做,但我不知道如何使返回参数的方法本身具有多线程


或者它根本不是关于多线程的,而我在这里没有看到一个明显的解决方案?

简短的回答:你不能。这不是异步处理的工作方式。在方法返回时,结果将永远不可用。事实上,您的方法在异步处理之前返回

正如ABakerSmith在他的评论中所说,您应该做的是添加一个完成处理程序闭包(aka block)作为函数的参数。这是调用者传入的代码块。编写方法是为了在异步JSON数据下载完成后调用完成块

然后在调用者中编写代码,以便在完成块中下载完成后,传入要执行的代码。您必须对程序进行结构化,使其能够在没有数据的情况下运行(例如,通过显示空的表视图),并且在数据到达后更新本身。您可以编写完成块,在表视图的数据模型中安装数据,然后在表视图上调用reloadData


如果编写异步方法,使其在主线程上执行完成块,通常会更安全。

在Swift/Objective-C开发中,有三种简单且完全预期的方法可以解决此问题,并且这两种解决方案都不涉及直接返回值的方法。您可以编写等待异步部分完成(阻塞线程)的代码,然后返回值,在某些情况下,这是在苹果自己的一些库中完成的,但我不打算介绍这种方法,因为这真的不是一个好主意


第一种方法涉及完成区块

当我们的方法要执行一些异步代码时,我们可以在异步工作完成时传入一个要执行的代码块。这种方法如下所示:

func getListFromOnline()->[String:String]{

    var resultList : [String:String]=[:]
    ...
    /*
    Some HTTP request is sent to the online source with NSURLRequest

    Then I parse the result and assign it to the resultList
    */
    ...

    return resultList
}
func asynchStuff(completionHandler: ([String:String]) -> Void) {
   // do asynchronous stuff, building a [String:String]
   let result: [String: String] = // the result we got async
   completionHandler(result)
}
记住在调用
asynchStuff
的同一线程上调用
completionHandler()
。(本例并未说明这一点。)


第二种方法涉及代表和协议

我们需要一个类来完成异步工作。此类将保存对委托的引用,委托将实现完成方法。第一,协议:

@objc protocol AsyncDelegate {
    func complete(result: [String:String]
}
现在,我们的异步工作者:

class AsyncWorker {
    weak var delegate: AsyncDelegate?

    func doAsyncWork() {
        // like before, do async work...
        let result: [String: String] = // the result we got async
        self.delegate?.complete(result)
    }
}
请记住,我们在调用
doAsyncWork()
的同一线程上调用完成委托方法。(本例并未说明这一点。)


第三种方法可以使用
NSNotificationCenter
完成。这是一种合适的方法,这种情况非常罕见,我甚至不会像其他两个例子那样为一个基本的例子操心,因为几乎可以肯定,在几乎所有的场景中,你都应该使用前两个例子中的一个


您使用哪种方法完全取决于您的特定用例。在Objective-C中,我经常倾向于委托而不是基于块的方法(尽管有时块是正确的),但Swift允许我们将常规函数/方法作为块参数传递,因此它使我稍微倾向于使用基于块的方法进行Swift,但仍然一如既往,使用正确的工具进行正确的工作


我想扩展这个答案,以解决在适当的线程上调用回调(无论是块还是委托)的问题。我不确定是否还有办法用GCD实现这一点,但我们可以用
NSOperationQueue
s实现

func asyncStuff(completionHandler: ([String:String]) -> Void) {
    let currentQueue = NSOperationQueue.currentQueue()
    someObject.doItsAsyncStuff(someArg, completion: { 
        result: [String:String] in
        currentQueue.addOperationWithBlock() {
            completionHandler(result)
        }
    }
}

下面是一个示例,说明如何在单独的线程中执行异步工作,然后返回主线程以更新UI

    let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
    dispatch_async(dispatch_get_global_queue(priority, 0)) {

        // do some asynchronous work

        dispatch_async(dispatch_get_main_queue()) {

            // use returned data to update some UI on the main thread
        }
    }

您可以使用一个完成处理程序,一旦得到结果就会调用它。否。应该在调用方法的线程上执行完成块。@nhgrif和Duncan:我不同意:应该在调用站点无法访问的私有“执行上下文”(线程、GCD队列、NSOperationQueue等)或“执行上下文”上调用完成块调用站点已显式指定的-如果有参数的话。这通常可以避免潜在的死锁和性能问题。在Mac OS/iOS上无法使用调用站点当前的执行上下文,因为没有隐式给出的“当前执行上下文”的抽象定义。这取决于您使用的是GCD还是NSO操作。我现在不能更新答案(在电话上),但我可以稍后再更新@Gozonery您仍然可以使用NSOperationQueues。。。当我有机会做一点自己的研究时,我将以任何方式更新这个答案。@GoZoner GCD
dispatch\u get\u current\u queue
已被弃用,但
NSOperationQueue
currentQueue
未被弃用。已经说过,<代码> NSURLSECTION/CODE>和AfNETWEB完全避免了这个问题(A)