更新数据 vs 更新 UI 困境

Updating Data vs Updating UI Dilemma

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

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

dispatch_async(dispatch_get_main_queue, {
    // Update UI
})

让我给出一个场景并阐明我的观点:

我有一个带有 UITableView 子视图的视图控制器。这个 table 视图将显示一些东西的列表(比如说用户名)。我准备并恢复了一个 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
}

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

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

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

Note: For the most part, UIKit classes should be used only from an application’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your application’s user interface in any way.

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

要事第一:

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 引用以防止任何可能的循环引用(内存泄漏)。

What is the difference of updating static data (array, dictionary, etc.) inside and outside of a dispatch_async call on main queue in completion handler of a http task (UI will be updated inside a dispatch call anyway, after the updates on my data)?. How can I ensure thread safety for background threads while reading from, inserting into or removing from an array?

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)

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

Does making a dispatch_async call inside a closure (for task completion handler) may cause any problem?

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