【问题标题】:A best practice for multithreading within a function that returns a value, Swift在返回值的函数中进行多线程处理的最佳实践,Swift
【发布时间】:2015-09-14 14:16:57
【问题描述】:

我有一个问题可能与实施无关,而是一个提示/最佳实践。

我正在使用 Swift 编写一个类,该类从在线源以 JSON 格式获取数据。我想在这个类中有特定的方法来连接到在线资源并以 Dictionary 类型返回结果。函数可能是这样的:

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 显然是返回之前从在线资源获取完成。毫不奇怪,它会导致程序失败。

有什么想法或提示我可以实现相反的效果吗?在这种情况下,我对多线程有点困惑,因为我想稍后从另一个类异步调用这个公共方法,我知道该怎么做,但我不知道如何制作 返回参数的方法 内部有多线程。

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

【问题讨论】:

  • 你可以使用一个完成处理程序,一旦你得到你的结果就会被调用。

标签: multithreading swift design-patterns architectural-patterns


【解决方案1】:

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


第一种方法涉及完成块。

当我们的方法要执行一些异步代码时,我们可以传入一个代码块,以便在异步工作完成时执行。这样的方法看起来像这样:

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 做到这一点,但我们可以用 NSOperationQueues 做到这一点。

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

【讨论】:

  • 取决于您使用的是 GCD 还是 NSOperations。我现在无法更新答案(通过电话),但我可以稍后更新。 @GoZoner
  • 你仍然可以使用 NSOperationQueues...当我有机会自己做一些研究时,我会更新这个答案。
  • @GoZoner GCD dispatch_get_current_queue 已弃用,但 currentQueueNSOperationQueue 未弃用。话虽如此,NSURLSession 和 AFNetworking 完全避免了这个问题,方法是 (a) 为哪个队列运行完成块的参数/属性; (b) 如果此队列为nil,则具有一些预定义的行为(NSURLSession,它将默认为自己的队列,AFNetworking 默认为主队列)。
  • @lakeoffm 如果您询问如何在接收和处理数据时更新 UI,那么除了像 nhgrif 所说的“完成”块或委托方法之外,您可能还有一个“进度”块或委托方法也是如此。有关基于块的示例,请参阅 AFNetworking。有关基于委托协议的示例,请参阅NSURLConnectionNSURLSession 的委托协议。顺便说一句,你应该考虑accepting nhgrif's answer。另外,不要在 cmets 中提出新问题,而是 (a) 尝试一下; (b) 如果需要,发布新问题。
  • 欢迎您发布自己的答案。你说得对。完成可以分派到它想要的任何队列。这不是要防止它,而是更多地使行为在某种程度上符合预期。队列异步调用应该返回到它被分派的地方(除非另有说明)。替代方案将分派到全局队列(并记录该行为)或接受队列以回调(并回调该队列)。我过去使用过所有这些方法。
【解决方案2】:

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

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

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

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

【讨论】:

  • 没有。您的完成块应该在调用您的方法的线程上执行。
  • @nhgrif 和 Duncan:我不同意:应该在调用站点无法访问的私有“执行上下文”(线程、GCD 队列、NSOperationQueue 等)上调用完成块,或在调用站点明确指定的“执行上下文”上 - 如果有一个参数。这通常可以避免潜在的死锁和性能问题。在 Mac OS / iOS 上使用调用点当前的执行上下文是不可能实现的 - 因为没有隐含给出的“当前执行上下文”的抽象定义。
【解决方案3】:

这里是一个示例,如何在单独的线程中执行异步工作,然后返回主线程以更新 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
        }
    }

【讨论】:

    猜你喜欢
    • 2020-09-15
    • 2010-10-08
    • 2018-11-01
    • 2017-07-23
    • 2015-09-01
    • 2012-02-03
    • 2013-05-12
    • 2018-09-12
    • 1970-01-01
    相关资源
    最近更新 更多