ios - 如何确保我的 DispatchQueue 专门在主线程上执行一些代码?
问题描述
我有一个管理数组的单例。这个单例可以从多个线程访问,所以它有自己的内部DispatchQueue
来管理跨线程的读/写访问。为简单起见,我们会说它是一个串行队列。
有时单例将从数组中读取并更新 UI。我该如何处理?
我的内部调度队列不知道哪个线程,对吧?这只是一个我不用担心的实现细节?在大多数情况下,这看起来不错,但在这一特定功能中,我需要确保它使用主线程。
可以按照以下方式做一些事情:
myDispatchQueue.sync { // Synchronize with internal queue to ensure no writes/reads happen at the same time
DispatchQueue.main.async { // Ensure that it's executed on the main thread
for item in internalArray {
// Pretend internalArray is an array of strings
someLabel.text = item
}
}
}
所以我的问题是:
- 可以吗?嵌套调度队列似乎很奇怪/错误。有没有更好的办法?也许像
myDispatchQueue.sync(forceMainThread: true) { ... }
什么? - 如果我没有使用
DispatchQueue.main.async { ... }
,并且我从主线程调用了该函数,我能否确定我的内部调度队列将在调用它的相同(主)线程上执行它?或者这也是一个“实现细节”,但它也可以在后台线程上调用?
基本上我很困惑线程似乎是一个你不应该担心队列的实现细节,但是当你确实需要担心时会发生什么?
简单示例代码:
class LabelUpdater {
static let shared = LabelUpdater()
var strings: [String] = []
private let dispatchQueue: dispatchQueue
private init {
dispatchQueue = DispatchQueue(label: "com.sample.me.LabelUpdaterQueue")
super.init()
}
func add(string: String) {
dispatchQueue.sync {
strings.append(string)
}
}
// Assume for sake of example that `labels` is always same array length as `strings`
func updateLabels(_ labels: [UILabel]) {
// Execute in the queue so that no read/write can occur at the same time.
dispatchQueue.sync {
// How do I know this will be on the main thread? Can I ensure it?
for (index, label) in labels.enumerated() {
label.text = strings[index]
}
}
}
}
解决方案
是的,您可以将一个调度嵌套到另一个队列的调度中。我们经常这样做。
但要非常小心。仅使用同步队列中的调度将异步调度包装到主队列是不够的。您的第一个示例不是线程安全的。您从主线程访问的那个数组可能正在从您的同步队列中发生变化:
这是一个竞争条件,因为您可能有多个线程(同步队列的线程和主线程)与同一个集合进行交互。与其让你的调度块objects
直接交互到主队列,你应该复制它,这就是你在调度到主队列中引用的内容。
例如,您可能想要执行以下操作:
func process(completion: @escaping (String) -> Void) {
syncQueue.sync {
let result = ... // note, this runs on thread associated with `syncQueue` ...
DispatchQueue.main.async {
completion(result) // ... but this runs on the main thread
}
}
}
这确保了主队列不与此类的任何内部属性交互,而只是在result
传递给syncQueue
.
请注意,所有这些都与它是单例无关。但既然你提出了这个话题,我建议不要使用单例来获取模型数据。它适用于接收器、无状态控制器等,但通常不建议用于模型数据。
我绝对不鼓励直接从单例启动 UI 控件更新的做法。我倾向于提供这些方法完成处理程序闭包,并让调用者处理生成的 UI 更新。当然,如果您想将闭包分派到主队列(为方便起见,在许多第三方 API 中很常见),那很好。但是单例不应该进入并更新 UI 控件本身。
我假设您所做的所有这些只是为了说明目的,但我向可能不理解这些担忧的未来读者添加了这个警告。
推荐阅读
- javascript - UTC 中的 Jquery 倒计时
- python - 如何使用 hdf5 文件中的一维数组并对其执行减法、加法等操作?
- android - 如何在没有消息模块的情况下在 React Native 中获取 Firebase 令牌(id)
- javascript - 如何使用 Puppeteer 通过 ElementHandle 设置 innerText?
- javascript - setTimeout 在 Firefox 中的行为怪异
- export-to-excel - 如何在熊猫中使用 to_excel 方法保存数据框的特定列
- c - 复制头文件时如何避免多个定义
- reactjs - 如何发送 firebase refreshToken ?反应
- c++ - 将元素插入范围
- swift - 如何为每个用户提供 Firebase 存储中的特定文件夹