除非保留,否则自定义 Combine Publisher 包装器 class 不起作用
Custom Combine Publisher wrapper class does not work unless retained
我为一些使用委托的旧代码写了一个 Combine Publisher 包装器 class。
TLDR;有人可以改进我管理自定义发布者生命周期的方式吗?最好使它的行为像普通发布者一样,您可以在其中沉迷于它而不必担心保留该实例。
详情
我遇到了一个问题,我必须保留对 Publisher 包装器的引用才能使其工作。自定义发布者的每个示例都没有此要求,尽管他们的发布者是结构并且与我的有很大不同。
这是我遇到的问题的简化版本。注意 doSomething()
中注释掉的部分
import Foundation
import Combine
// Old code that uses delegate
protocol ThingDelegate: AnyObject {
func delegateCall(number: Int)
}
class Thing {
weak var delegate: ThingDelegate?
var name: String = "Stuff"
init() {
Swift.print("thing init")
}
deinit {
Swift.print("☠️☠️☠️☠️☠️☠️ thing deinit")
}
func start() {
Swift.print("Thing.start()")
DispatchQueue.main.async {
self.delegate?.delegateCall(number: 99)
}
}
}
// Combine Publisher Wrapper
class PublisherWrapper: Publisher {
typealias Output = Int
typealias Failure = Error
private let subject = PassthroughSubject<Int, Failure>()
var thing: Thing
init(thing: Thing) {
Swift.print("wrapper init")
self.thing = thing
self.thing.delegate = self
}
deinit {
Swift.print("☠️☠️☠️☠️☠️☠️ wrapper deinit")
}
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Int == S.Input {
self.subject.subscribe(subscriber)
self.thing.start()
}
}
extension PublisherWrapper: ThingDelegate {
func delegateCall(number: Int) {
Swift.print("publisher delegate call: \(number)")
self.subject.send(number)
self.subject.send(completion: .finished)
}
}
class Test {
var cancellables = Set<AnyCancellable>()
var wrapper: PublisherWrapper?
func doSomething() {
Swift.print("doSomething()")
let thing = Thing()
let wrapper = PublisherWrapper(thing: thing)
self.wrapper = wrapper
// Take a look over here
//
// if you comment out the line above where I set self.wrapper = wrapper
// it prints out the following
//
//start
//doSomething()
//thing init
//wrapper init
//Thing.start()
//☠️☠️☠️☠️☠️☠️ wrapper deinit
//☠️☠️☠️☠️☠️☠️ thing deinit
//
// But if you uncomment the line and retain it and you'll get the following
//start
//doSomething()
//thing init
//wrapper init
//Thing.start()
//publisher delegate call: 99
//value: 99
//finished
//release wrapper: nil
//☠️☠️☠️☠️☠️☠️ wrapper deinit
//☠️☠️☠️☠️☠️☠️ thing deinit
// we get the value and everything works as it should
wrapper.sink { [weak self] completion in
print(completion)
self?.wrapper = nil
print("release wrapper: \(self?.wrapper)")
} receiveValue: {
print("value: \([=11=])")
}.store(in: &self.cancellables)
}
}
print("start")
let t = Test()
t.doSomething()
有没有一种方法可以避免像这样保留发布者?我问是因为在使用 flatMap 时这会变得非常难看。
一个解决方案是实现自定义 Subscription
对象。
我会实施一个简单的协议,您可以为所有具有委托的 类 遵守该协议。
protocol Provider {
associatedtype Output
func start(provide: @escaping (Output) -> Void)
}
我在这里实现了一个 Publisher
,我可以为 Provider
提供数据。发布者真正做的就是创建一个 Subscription
对象并将其连接到传递给 receive(subscriber:)
方法的 Subscriber
。自定义 Subscription
对象完成所有繁重的工作。因此,我们可以将 Publisher
定义为结构。
Subscription
对象从 Provider
接收数据并将其传递给 Subscriber
。请注意,Subscription
对象需要保存对 Provider
的引用,因此它不会被释放。
extension Publishers {
struct Providable<ProviderType: Provider>: Publisher {
typealias Output = ProviderType.Output
typealias Failure = Never
private class Subscription<SubscriberType: Subscriber>: Combine.Subscription {
private let provider: ProviderType
init(
provider: ProviderType,
subscriber: SubscriberType
) where SubscriberType.Input == ProviderType.Output {
self.provider = provider
provider.start { value in
_ = subscriber.receive(value)
subscriber.receive(completion: .finished)
}
}
deinit {
Swift.print("provided subscription deinit")
}
func request(_ demand: Subscribers.Demand) {}
func cancel() {}
}
private let provider: ProviderType
init(provider: ProviderType) {
self.provider = provider
}
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(subscription: Subscription(provider: provider, subscriber: subscriber))
}
}
}
缺点是您需要为每个要用 Combine
包装的委托对象实现自定义 Provider
对象:
final class ThingOutputProvider: Provider, ThingDelegate {
private let thing: Thing
private var provide: (Int) -> Void = { _ in }
init(thing: Thing) {
self.thing = thing
self.thing.delegate = self
}
func start(provide: @escaping (Int) -> Void) {
self.provide = provide
self.thing.start()
}
func delegateCall(number: Int) {
provide(number)
}
}
这是一个方便的小协议扩展,因此我们可以为我们的 Provider
创建发布者:
extension Provider {
var publisher: Publishers.Providable<Self> {
Publishers.Providable(provider: self)
}
}
用法如下:
class Test {
var cancellables = Set<AnyCancellable>()
func doSomething() {
ThingOutputProvider(thing: Thing())
.publisher
.sink { [weak self] completion in
print("completion: \(completion)")
self?.cancellables.removeAll()
} receiveValue: {
print("value: \([=14=])")
}.store(in: &self.cancellables)
}
}
无需维护对 Publisher
的引用即可工作的原因是 Subscription
对象在 Combine
管道的生命周期内保持活动状态。
希望这对您有所帮助。
我为一些使用委托的旧代码写了一个 Combine Publisher 包装器 class。
TLDR;有人可以改进我管理自定义发布者生命周期的方式吗?最好使它的行为像普通发布者一样,您可以在其中沉迷于它而不必担心保留该实例。
详情 我遇到了一个问题,我必须保留对 Publisher 包装器的引用才能使其工作。自定义发布者的每个示例都没有此要求,尽管他们的发布者是结构并且与我的有很大不同。
这是我遇到的问题的简化版本。注意 doSomething()
中注释掉的部分import Foundation
import Combine
// Old code that uses delegate
protocol ThingDelegate: AnyObject {
func delegateCall(number: Int)
}
class Thing {
weak var delegate: ThingDelegate?
var name: String = "Stuff"
init() {
Swift.print("thing init")
}
deinit {
Swift.print("☠️☠️☠️☠️☠️☠️ thing deinit")
}
func start() {
Swift.print("Thing.start()")
DispatchQueue.main.async {
self.delegate?.delegateCall(number: 99)
}
}
}
// Combine Publisher Wrapper
class PublisherWrapper: Publisher {
typealias Output = Int
typealias Failure = Error
private let subject = PassthroughSubject<Int, Failure>()
var thing: Thing
init(thing: Thing) {
Swift.print("wrapper init")
self.thing = thing
self.thing.delegate = self
}
deinit {
Swift.print("☠️☠️☠️☠️☠️☠️ wrapper deinit")
}
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Int == S.Input {
self.subject.subscribe(subscriber)
self.thing.start()
}
}
extension PublisherWrapper: ThingDelegate {
func delegateCall(number: Int) {
Swift.print("publisher delegate call: \(number)")
self.subject.send(number)
self.subject.send(completion: .finished)
}
}
class Test {
var cancellables = Set<AnyCancellable>()
var wrapper: PublisherWrapper?
func doSomething() {
Swift.print("doSomething()")
let thing = Thing()
let wrapper = PublisherWrapper(thing: thing)
self.wrapper = wrapper
// Take a look over here
//
// if you comment out the line above where I set self.wrapper = wrapper
// it prints out the following
//
//start
//doSomething()
//thing init
//wrapper init
//Thing.start()
//☠️☠️☠️☠️☠️☠️ wrapper deinit
//☠️☠️☠️☠️☠️☠️ thing deinit
//
// But if you uncomment the line and retain it and you'll get the following
//start
//doSomething()
//thing init
//wrapper init
//Thing.start()
//publisher delegate call: 99
//value: 99
//finished
//release wrapper: nil
//☠️☠️☠️☠️☠️☠️ wrapper deinit
//☠️☠️☠️☠️☠️☠️ thing deinit
// we get the value and everything works as it should
wrapper.sink { [weak self] completion in
print(completion)
self?.wrapper = nil
print("release wrapper: \(self?.wrapper)")
} receiveValue: {
print("value: \([=11=])")
}.store(in: &self.cancellables)
}
}
print("start")
let t = Test()
t.doSomething()
有没有一种方法可以避免像这样保留发布者?我问是因为在使用 flatMap 时这会变得非常难看。
一个解决方案是实现自定义 Subscription
对象。
我会实施一个简单的协议,您可以为所有具有委托的 类 遵守该协议。
protocol Provider {
associatedtype Output
func start(provide: @escaping (Output) -> Void)
}
我在这里实现了一个 Publisher
,我可以为 Provider
提供数据。发布者真正做的就是创建一个 Subscription
对象并将其连接到传递给 receive(subscriber:)
方法的 Subscriber
。自定义 Subscription
对象完成所有繁重的工作。因此,我们可以将 Publisher
定义为结构。
Subscription
对象从 Provider
接收数据并将其传递给 Subscriber
。请注意,Subscription
对象需要保存对 Provider
的引用,因此它不会被释放。
extension Publishers {
struct Providable<ProviderType: Provider>: Publisher {
typealias Output = ProviderType.Output
typealias Failure = Never
private class Subscription<SubscriberType: Subscriber>: Combine.Subscription {
private let provider: ProviderType
init(
provider: ProviderType,
subscriber: SubscriberType
) where SubscriberType.Input == ProviderType.Output {
self.provider = provider
provider.start { value in
_ = subscriber.receive(value)
subscriber.receive(completion: .finished)
}
}
deinit {
Swift.print("provided subscription deinit")
}
func request(_ demand: Subscribers.Demand) {}
func cancel() {}
}
private let provider: ProviderType
init(provider: ProviderType) {
self.provider = provider
}
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(subscription: Subscription(provider: provider, subscriber: subscriber))
}
}
}
缺点是您需要为每个要用 Combine
包装的委托对象实现自定义 Provider
对象:
final class ThingOutputProvider: Provider, ThingDelegate {
private let thing: Thing
private var provide: (Int) -> Void = { _ in }
init(thing: Thing) {
self.thing = thing
self.thing.delegate = self
}
func start(provide: @escaping (Int) -> Void) {
self.provide = provide
self.thing.start()
}
func delegateCall(number: Int) {
provide(number)
}
}
这是一个方便的小协议扩展,因此我们可以为我们的 Provider
创建发布者:
extension Provider {
var publisher: Publishers.Providable<Self> {
Publishers.Providable(provider: self)
}
}
用法如下:
class Test {
var cancellables = Set<AnyCancellable>()
func doSomething() {
ThingOutputProvider(thing: Thing())
.publisher
.sink { [weak self] completion in
print("completion: \(completion)")
self?.cancellables.removeAll()
} receiveValue: {
print("value: \([=14=])")
}.store(in: &self.cancellables)
}
}
无需维护对 Publisher
的引用即可工作的原因是 Subscription
对象在 Combine
管道的生命周期内保持活动状态。
希望这对您有所帮助。