returns 一个值 Swift 的函数内多线程的最佳实践

A best practice for multithreading within a function that returns a value, Swift

我有一个问题可能与实施无关,而是 tip/best 练习。

我正在 Swift 中开发 class,它以 JSON 格式从在线来源获取数据。我想在这个 class 中有特定的方法连接到在线源并以字典类型返回结果。函数可能是这样的:

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
}

我现在在这个实现中得到的,没有多线程,resultList 显然在 从在线源获取完成之前返回。毫不奇怪,它导致程序失败。

有什么想法或技巧可以达到相反的效果吗?在这种情况下,我对多线程有点困惑,因为我想稍后从另一个 class 异步调用此 public 方法,我知道该怎么做,但我不知道如何使returns 参数 本身具有多线程的方法。

或者它根本与多线程无关,我在这里没有看到明显的解决方案?

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

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

然后在调用程序中编写代码以在下载完成后传入要执行的代码在完成块中。您必须构建您的程序,以便它可以在没有您的数据的情况下运行(例如,通过显示一个空的 table 视图)并在数据到达后自行更新。您可以编写完成块以在 table 视图的数据模型中安装数据,然后在 table 视图上调用 reloadData。

编写异步方法通常会更安全,因此它会在主线程上执行完成块。

There are three easy and completely expect approaches to this problem in Swift/Objective-C development, and neither of these solutions involve the method returning the value directly. You could write code that waited for the asynchronous part to complete (blocking a thread) and then return the value, and there are cases where this is done in some of Apple's own libraries, but I'm not going to cover the approach, because it's really not that great of an idea.


The first approach involves completion blocks.

When our method is going to perform some asynchronous code, we can pass in a block of code to execute whenever the asynchronous work is done. Such a method would look like this:

func asynchStuff(completionHandler: ([String:String]) -> Void) {
   // do asynchronous stuff, building a [String:String]
   let result: [String: String] = // the result we got async
   completionHandler(result)
}

Remember to call completionHandler() on the same thread that asynchStuff was called on. (This example does not demonstrate that.)


The second approach involves delegates and protocols.

We need a class to do the asynchronous work. This class will hold a reference to our delegate, which will implement completion methods. First, a protocol:

@objc protocol AsyncDelegate {
    func complete(result: [String:String]
}

Now our async worker:

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

Remember to be certain that we're calling the completion delegate method on the same thread that doAsyncWork() was called on. (This example does not demonstrate that.)


The third approach can be done using NSNotificationCenter. The times when this is the appropriate approach are going to be so rare that I'm not even going to bother with even a rudimentary example like I did the other two examples because you should almost certainly be using one of the first two examples in almost every scenario.


Which approach you use depends entirely on your specific use case. In Objective-C, I frequently preferred delegation to block based approach (although sometimes blocks are correct), but Swift allows us to pass regular functions/methods as our block argument so it leans me slightly back toward using the block-based approach for Swift, but still, use the right tool for the right job, as always.


I wanted to expand this answer to address calling the callback (whether blocks or delegates) on the appropriate thread. I'm not sure if there's a way to do this with GCD still, but we can do it with NSOperationQueues.

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