如果说 Publisher 决定了发布什么样的 (what) 数据的话,Scheduler(调度器) 所要解决的就是两个问题:在什么地方 (where),以及在什么时候 (when) 来发布数据和接收数据。我们都知道,在 iOS 开发中如果需要更新 UI,需要保证相关操作发生在主线程。在 Combine 中如果数据流前面的 Publisher 是在后台线程进行操作,那么在订阅时,当状态的变化会更新 UI 时,需要将数据流中接收数据的线程切换到主线程。
Scheduler
在是一个协议,遵守了该协议的内置 Scheduler 有:
DispatchQueue
OperationQueue
RunLoop
ImmediateScheduler
:立即执行同步操作, 如果使用它执行延迟的工作,会报错。使用RunLoop.main
, DispatchQueue.main
和OperationQueue.main
来执行与 UI 相关的操作。
默认情况下,当前的 Scheduler 与最初产生数据的 Publisher 所在的 Scheduler 相同。但是实际情况往往是在整个数据流中需要切换 Scheduler,所以 Combine 提供了两个函数来设置 Scheduler。
定义了在哪个 Scheduler 完成 Publisher 的订阅。(在哪里接收数据)
import Combine
let subscription = Just(1)
.map { _ in print(Thread.isMainThread) }
.receive(on: DispatchQueue.global())
.map { print(Thread.isMainThread) }
.sink { print(Thread.isMainThread) }
/* 输出
true
false
false
*/
定义了在哪个 Scheduler 来发布 Publisher,它的位置顺序不会影响结果。(在哪里发布数据)
import Combine
let subscription = Just(1)
.subscribe(on: DispatchQueue.global())
.map { _ in print(Thread.isMainThread) }
.sink { print(Thread.isMainThread) }
/* 输出
false
false
*/
let subscription = Just(1)
.subscribe(on: DispatchQueue.global())
.map { _ in print(Thread.isMainThread) }
.receive(on: DispatchQueue.main)
.sink { print(Thread.isMainThread) }
/* 输出
false
true
*/
import UIKit
import Combine
let subscription = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://www.example.com")!)
.compactMap { String(data: $0.data, encoding: .utf8) }
.receive(on: RunLoop.main) // 回到主线程更新UI
.sink {
textView.text = $0
}