Combine Swift 中 connect() 与 autoconnect() 的隐藏力量
I’m happy to translate the article for you, but I’ll need the full text of the article (the content you’d like translated). Could you please paste the article’s body here? Once I have that, I’ll provide a Simplified Chinese translation while keeping the source link and all formatting exactly as you requested.
介绍
当你订阅一个 Combine 发布者时,你可能会期待值立即开始流动。然而,某些发布者会等待明确的信号才开始工作。可连接的发布者为你提供了这种控制,而 connect() 和 autoconnect() 是启动它们的两种方式。
connect() – 手动控制
一个 可连接的发布者 在你调用其 connect() 方法之前什么也不做。这让你可以:
- 在发出任何值之前设置多个订阅者。
- 将数据流的启动与其他应用事件协调。
- 在准备好之前避免昂贵的工作。
示例
import Combine
let subject = PassthroughSubject<Int, Never>()
// 创建一个可连接的发布者
let connectable = subject
.print("Debug")
.makeConnectable()
// 首先订阅
let subscription1 = connectable
.sink { value in
print("Subscriber 1 received: \(value)")
}
let subscription2 = connectable
.sink { value in
print("Subscriber 2 received: \(value)")
}
// 此时什么也不会发生,尽管我们已经有了订阅者
// 手动启动发布者
let connection = connectable.connect()
// 现在值会流向所有订阅者
subject.send(1)
subject.send(2)
关键点
- 手动启动 – 你决定发布者何时开始。
- 多个订阅者 – 一旦连接,所有订阅者都会收到值。
- 可取消 –
connection可以被取消以停止数据流。
何时使用 connect()
- 需要等到多个订阅者都准备好时。
- 想要将启动时机与其他事件同步。
- 上游工作代价高昂,且只应执行一次。
实际场景:共享网络请求
let url = URL(string: "https://example.com/user")!
let publisher = URLSession.shared
.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: User.self, decoder: JSONDecoder())
.share()
.makeConnectable()
// 设置多个订阅者
let sub1 = publisher.sink(
receiveCompletion: { _ in },
receiveValue: { user in updateUI(user) }
)
let sub2 = publisher.sink(
receiveCompletion: { _ in },
receiveValue: { user in saveToCache(user) }
)
// 只会执行一次网络请求
let connection = publisher.connect()
autoconnect() – 自动启动
autoconnect() 消除了手动调用 connect() 的需求。发布者会在出现 第一个 订阅者时自动连接。
示例
let timer = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
let subscription = timer.sink { date in
print("Timer fired at: \(date)")
}
关键点
- 立即启动 – 发布者在获得第一个订阅者后立即开始。
- 方便 – 不需要单独保存连接。
- 计时器常用 – 适用于周期性事件。
何时使用 autoconnect()
- 你希望发布者在订阅后立即启动。
- 正在处理计时器或其他周期性来源。
- 不需要对多个订阅者进行精确协调。
ViewModel 示例
class ViewModel: ObservableObject {
@Published var seconds = 0
private var cancellables = Set<AnyCancellable>()
func startTimer() {
Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.seconds += 1
}
.store(in: &cancellables)
}
}
在 connect() 与 autoconnect() 之间的选择
| 需求 | 使用 connect() | 使用 autoconnect() |
|---|---|---|
| 需要等待多个订阅者 | ✅ | ❌ |
| 订阅时立即启动 | ❌ | ✅ |
| 计时器或周期性事件 | ❌(可能) | ✅ |
| 昂贵的共享操作 | ✅ | ✅(如果立即启动没问题) |
| 简单用例 | ❌ | ✅ |
| 与其他事件的协调 | ✅ | ❌ |
共享单个订阅
通常,你会将 share()(它会创建一个可连接的发布者)与 connect() 或 autoconnect() 结合使用,以确保多个下游订阅者共享同一上游工作。
let shared = expensivePublisher
.share() // 使发布者可连接
.autoconnect() // 自动启动(或手动使用 .connect())
let sub1 = shared.sink { print("A: \($0)") }
let sub2 = shared.sink { print("B: \($0)") }
存储连接和订阅
请记得保留对连接和可取消对象的引用,否则它们会被释放,导致发布者停止工作。
class DataManager {
private var connection: Cancellable?
private var subscriptions = Set<AnyCancellable>()
func setupConnectable() {
let connectable = publisher.makeConnectable()
connectable
.sink { value in print(value) }
.store(in: &subscriptions)
// 将连接保存下来,以便以后可以取消
connection = connectable.connect()
}
deinit {
connection?.cancel()
}
}
结论
connect() 和 autoconnect() 为您提供对 Combine 流何时开始发出值的精确控制。当您需要明确的时机和协调时使用 connect(),而在您更倾向于简洁和立即启动时使用 autoconnect()——尤其是计时器或其他周期性发布者。了解这些工具可以让您在 Swift 应用中更有效地管理资源使用和数据流。