如何取消接收器内的组合订阅?

How do I cancel a combine subscription within a sink?

我的应用程序中的某个功能的架构有些复杂。

示例代码如下。我最初的期望是这只会打印一次,因为我调用了 cancellableSet.removeAll()。但这实际上最终被调用了两次,这在我的应用程序中造成了问题。

我如何获得它,以便它仅在订阅存储在可取消集中后触发 sink 中的内容。

请注意,我将在此处提及一些限制。我的示例代码只是对此进行了简化。

import Combine

enum State {
    case loggedOut
    case doingSomething
}

let aState = CurrentValueSubject<State, Never>(.doingSomething)
private var cancellableSet: Set<AnyCancellable> = []

func logUserOut() {
    cancellableSet.removeAll()
    aState.send(.loggedOut)
}

func doSomethingElse() { }
aState.sink { newState in
    print("numberOfSubscriptions is: \(cancellableSet.count)")
    switch newState {
    case .loggedOut:
        doSomethingElse()
    case .doingSomething:
        logUserOut()
    }
    
}
.store(in: &cancellableSet)

如果此代码所在的队列 运行 是一个串行队列,那么也许您可以将接收器内代码的执行移动到队列的末尾。这样,程序会找到时间将订阅存储在集合中。

aState.sink { newState in
    DispatchQueue.main.async { // or whatever other queue you are running on
        print("numberOfSubscriptions is: \(cancellableSet.count)")
        switch newState {
        case .loggedOut:
            doSomethingElse()
        case .doingSomething:
            logUserOut()
        }
    }
}
.store(in: &cancellableSet)

虽然有点脏。

您的代码中的问题是订阅在调用 sink returns 之前开始同步传递值,因此甚至在调用 store 开始之前。

解决这个问题的一种方法是在订阅前将 aState 变成 ConnectablePublisherConnectablePublisher 在其 connect 方法被调用之前不会发布。所以调用connect after store returns.

您可以在 Failure == Never 的任何 Publisher 上使用 makeConnectable 方法将其包装在 ConnectablePublisher.

let connectable = aState.makeConnectable()
connectable.sink { newState in
    print("numberOfSubscriptions is: \(cancellableSet.count)")
    switch newState {
    case .loggedOut:
        doSomethingElse()
    case .doingSomething:
        logUserOut()
    }
}
.store(in: &cancellableSet)
connectable.connect()