Combine订阅者Subscriber
Combine订阅者Subscriber

Combine订阅者Subscriber

Subscriber负责订阅Publisher,接收其发布的数据,只有订阅后才能接收到数据。

订阅方式

1、sink

sink是Combine中最常用、最直观的订阅方式,本质上就是一个“订阅者构造器”。

public func sink(receiveCompletion: @escaping (Subscribers.Completion<Self.Failure>) -> Void, receiveValue: @escaping (Self.Output) -> Void) -> AnyCancellable

sink同时接收receiveCompletion和receiveValue两个闭包。

1、receiveCompletion负责接收“流结束”:

enum Completion<Failure: Error> {
    case finished
    case failure(Failure)
}

当发布者抛出错误时,receiveCompletion会接收错误并结束,不再订阅。

2、receiveValue负责接收发布的值,Output在Publisher定义中表示发布的值。

3、AnyCancellable表示订阅关系,使用sink时需要被持有,如果没有人持有AnyCancellable,则会立即被销毁,订阅立即取消。

示例

.sink(receiveCompletion: { completion in
    // 订阅完成(正常完成或失败)时调用
    switch completion {
    case .finished:
        print("正常完成")
    case .failure(let failure):
        print("失败")
    }
}, receiveValue: { value in
    // 每次收到 Publisher 发出的值时调用
    print("value:\(value)")
})

当发布者(这里是somePublisher)发布新值后,sink 可以处理完成的状态以及接收发布的值。

4、在不关心receiveCompletion的情况下,还可以使用sink简写形式:

sink { value in ... }

只监听值,不处理结束或错误。

5、如果Publisher不再发布新值,可以使用 send(completion:_) 方法结束:

message.send(completion: .finished) // 正常结束
message.send(completion: .failure(Error))   // 抛出一个错误

receiveCompletion会接收到结束或错误,根据失败类型进行处理。

注意:只有支持失败的Publisher的类型(比如PassthroughSubject<String, Error>)才可以发送结束/抛出错误,否则无法使用send(completion:_)方法。

6、sink返回一个AnyCancellable类型,用于管理订阅,可以被取消。

2、assign

assign的作用为把Publisher发出的值,自动赋给某个对象的属性。

assign<Root: AnyObject>(
    to keyPath: ReferenceWritableKeyPath<Root, Output>,
    on object: Root
) -> AnyCancellable

Output为Publisher发出的值,keyPath为目标对象的可写属性,object为对象实例。

例如:

publisher.assign(to: \.text, on: label)

这表示将publisher发出的值,写入label对象的text属性。

注意:text属性需要为可写,并且为 @Publsihed 修饰。

在SwiftUI中,通常使用:

publisher.assign(to: &$viewModel.text)  // 写入viewModel的text属性
publisher.assign(to: \.text, on: self)  // 写入当前对象/视图的text属性

和sink是等价的:

publisher.sink { value in
    self.text = value
}

因此,assign可以被理解为只能写属性的sink,通常用于数据绑定。

assign存在一些限制,比如只能用于不会失败的Publisher,因为assign没有地方处理失败情况。另外,也只能用于引用类型的对象。

3、onReceive

在SwiftUI中,推荐使用 onReceive() 代替 sink,因为onReceive可以自动管理生命周期(绑定视图),sink需要手动保留AnyCancellable,否则被释放后,不再接收事件。

let pastePublisher = PassthroughSubject<Void, Never>()

.onReceive(pastePublisher) {
    // 每次调用 pastePublisher.send(),都会触发这里
}

onReceive订阅Combine发布者,当发布者发布时间时调用闭包。

详细内容请见《SwiftUI响应Combine的onReceive》。

订阅关系AnyCancellable

AnyCancellable是类型擦拭(Type Erasure)的产物,表示每种订阅都有不同的具体类型,Combine不关系具体细节,所以统一包装成 AnyCancellable。

当调用 sink 时,Combine会创建一条订阅管道,并返回“控制管道的权限”,这个权限就是AnyCancellable。

因为订阅不是一次函数调用,而是一个持续关系:

publisher.sink { ... }

发布者发布新值时,会通知订阅者,这段关系存在建立-维持-取消共三个阶段,AnyCancellable就是这个关系的实体对象。

错误示范:在onAppear中设置 sink:

.onAppear {
    let Msg = message
        .sink { item in
            num = item
            print("接受的值为:\(item)")
        }
}

当发布者message发布新值时,并不会输出任何内容。

因为在onAppear中Msg是局部变量,当onAppear结束时,Msg被释放,订阅立即取消,Combine严格依赖ARC,不会自动保存订阅。

持有AnyCancellable的方式

AnyCancellable主要有三种持有方式:

1、手动持有

var cancellable: AnyCancellable?
cancellable = publisher.sink { ... }

2、批量管理

var cancellables = Set<AnyCancellable>()

publisher
    .sink { ... }
    .store(in: &cancellables)

Combine的推荐写法。

两种方式都适合在ViewController、ViewModel等长生命周期的对象。

3、SwiftUI自动持有

在SwiftUI中使用onReceive时:

.onReceive(message) { item in
    print("接受的值为:\(item)")
}

SwiftUI会自动创建sink,保存AnyCancellable,并在视图消失时自动cancel,因此不需要手动持有AnyCancellable。

4、取消订阅

订阅后返回的对象,可以通过调用 .cancel() 停止订阅:

cancellable.cancel()

订阅取消后,Publisher仍然可以发送消息,但是没有人再监听,取消订阅可以避免内存泄漏。

总结

.sink创建订阅关系,通过创建AnyCancellable类型的变量持有订阅关系,最后可以使用 cancel() 取消订阅关系。

相关文章

1、Apple处理异步任务的Combine框架

2、Combine发布者Publisher

3、SwiftUI响应Combine的onReceive

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注