【问题标题】:Updating Data vs Updating UI Dilemma更新数据与更新 UI 的困境
【发布时间】:2015-02-23 10:02:35
【问题描述】:

我最近使用 Swift 开发了一个 iOS 应用程序,它处理大量后台 HTTP 任务,不仅更新 UI,还根据响应数据更新当前会话的静态数据(大量数组、变量等)。我在 iOS 开发中可能算得上是新人,有几点我很困惑:

从后台任务更新 UI 是通过 GCD API 处理的。我一直使用以下方法处理这些更新:

dispatch_async(dispatch_get_main_queue, {
    // Update UI
})

让我给出一个场景并澄清我的观点:

我有一个带有 UITableView 子视图的视图控制器。这个表视图将显示一些东西的列表(比如说用户名)。我准备并恢复了一个 NSURLSessionDataTask:

let request = NSMutableURLRequest(URL: someURL)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
    data, response, error in

    // Handle error case
    // Parse data, and receive a user list
    // AppData.userList = parsed list
    // Update table view
}

我的一些测试人员遇到了一些与调度调用和运行循环相关的崩溃,我无法找到背后的原因。我认为这与我的调度呼叫有关。现在我正在重新考虑我的设计,这是我的问题:

  • 在 http 任务的完成处理程序中,在主队列上的 dispatch_async 调用内部和外部更新静态数据(数组、字典等)有什么区别(UI 无论如何都会在调度调用内部更新,在更新之后在我的数据上)?如何在读取、插入或删除数组时确保后台线程的线程安全?
  • 在闭包内进行 dispatch_async 调用(用于任务完成处理程序)是否会导致任何问题?

任何明确的评论或指导都会非常有帮助!现在已经非常感谢了

【问题讨论】:

    标签: ios asynchronous nsurlsessiondatatask


    【解决方案1】:

    即使我对答案没有清晰的看法,我也会尝试给出答案。
    您必须从主线程更新您的 UI,因为 UIKit 对象(如果您想在屏幕外的位图上下文上绘制,会有一些例外)不是线程安全的。
    以下是苹果对此的评价:

    注意:在大多数情况下,UIKit 类只能从 应用程序的主线程。对于类来说尤其如此 派生自 UIResponder 或涉及操纵您的 应用程序的用户界面。

    所有渲染例程都应该在主线程上运行,这很可能是由于 GPU 加速和事件管理。
    相比之下 Foundation 对象(除了一些可变对象是线程安全的),因此可以在不同的线程上管理/操作和使用它们。
    线程安全意味着您可以轻松地在线程之间共享对象。
    如果您在后台线程上使用 Foundation 对象,则根本没有问题,如果您在该线程内使用 mutable 一次,一切都应该工作,当您想将对象添加到数组(例如)时,就会出现可变对象的问题更多线程。
    如果你提供自己的类,你应该自己提供线程安全。

    【讨论】:

      【解决方案2】:

      第一件事:

      let request = NSMutableURLRequest(URL: someURL)
      let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { [weak self]
      (data, response, error) in
          if let weakself = self {
              // ...
              dispatch_async(dispatch_get_main_queue()) {
                  // update UI
              }
          }
      }
      

      无论何时进行异步调用,都需要确保没有self 引用,以防止任何可能的循环引用(内存泄漏)。

      在 http 任务的完成处理程序中,在主队列上的 dispatch_async 调用内部和外部更新静态数据(数组、字典等)有什么区别(UI 无论如何都会在调度调用内部更新,在更新之后在我的数据上)?如何在读取、插入或删除数组时确保后台线程的线程安全?

      dispatch_async 内部和外部更新数据没有区别。您只需要确保在遍历数组或字典时不会对其进行变异。您可以通过锁定数据结构或创建临时浅表副本来做到这一点。

      例如,如果您正在读取可能被另一个线程更改的数组:

      var integers = Array<Int>()
      
      // The following creates an IMMUTABLE shallow copy of mutable array
      let ints = integers
      
      for value in ints {
         // use value
      }
      
      // OR use locking
      objc_sync_enter(integers)
      for value in integers {
         // use value
      }
      objc_sync_exit(integers)
      
      // in another thread - lock before mutating
      objc_sync_enter(integers)
      integers.append(someIntValue)
      objc_sync_exit(integers)
      

      当然,您可以使用其他锁定机制。但重点是,您只需要确保以线程安全的方式访问数据。

      在闭包内进行 dispatch_async 调用(用于任务完成处理程序)是否会导致任何问题?

      答案是否定的。只要您确保这些闭包中不存在对self 的引用,并且竞争线程可访问的数据以线程安全的方式访问/更改。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-03
        • 2018-01-04
        • 2012-03-18
        相关资源
        最近更新 更多