Как правильно управлять коллекцией AnyCancellable - PullRequest
1 голос
/ 05 ноября 2019

Я бы хотел, чтобы все издатели выполняли, если явно не отменены. Я не возражаю против того, чтобы AnyCancellable выходил из области видимости, однако, основываясь на документах, он автоматически вызывает cancel на deinit, что нежелательно.

Я пытался использовать сумку с возможностью отмены, но AnyCancelable продолжал накапливаться даже после того, как издатель уволил завершение.

Должен ли я управлять сумкой вручную? У меня сложилось впечатление, что store(in: inout Set) должен был быть использован для удобства управления отменяемыми экземплярами, однако все, что он делает, это толкает AnyCancellable в набор.

var cancelableSet = Set<AnyCancellable>()

func work(value: Int) -> AnyCancellable {
    return Just(value)
        .delay(for: .seconds(1), scheduler: DispatchQueue.global(qos: .default))
        .map { $0 + 1 }
        .sink(receiveValue: { (value) in
            print("Got value: \(value)")
        })
}

work(value: 1337).store(in: &cancelableSet)

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
    print("\(cancelableSet)")
}

То, что я до сих пор придумал,который отлично работает, но заставляет задуматься, что-то отсутствует в платформе Combine или не предназначено для такого использования:

class DisposeBag {
    private let lock = NSLock()
    private var cancellableSet = Set<AnyCancellable>()

    func store(_ cancellable: AnyCancellable) {
        print("Store cancellable: \(cancellable)")

        lock.lock()
        cancellableSet.insert(cancellable)
        lock.unlock()
    }

    func drop(_ cancellable: AnyCancellable) {
        print("Drop cancellable: \(cancellable)")

        lock.lock()
        cancellableSet.remove(cancellable)
        lock.unlock()
    }
}

extension Publisher {

    @discardableResult func autoDisposableSink(disposeBag: DisposeBag, receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable {
        var sharedCancellable: AnyCancellable?

        let disposeSubscriber = {
            if let sharedCancellable = sharedCancellable {
                disposeBag.drop(sharedCancellable)
            }
        }

        let cancellable = self.handleEvents(receiveCompletion: { _ in
            disposeSubscriber()
        }, receiveCancel: {
            disposeSubscriber()
        }).sink(receiveCompletion: receiveCompletion, receiveValue: receiveValue)

        sharedCancellable = cancellable
        disposeBag.store(cancellable)

        return cancellable
    }

}

1 Ответ

1 голос
/ 05 ноября 2019

Подписки в Apple Combine ограничены в соответствии с RAII. Т.е. событие деинициализации эквивалентно событию автоматического удаления наблюдаемого. Это противоречит RxSwift Disposable, где это поведение иногда воспроизводится, но не совсем так.

Даже в RxSwift, если вы потеряете DisposeBag, ваши подписки будут удалены, и это особенность. Если вы хотите, чтобы ваша подписка прошла через область действия, это означает, что она принадлежит внешней области.

И ни одна из этих реализаций не занята, фактически выбрасывая Disposables из дерева хранения после завершения подписки. .

...