Удалить из массива AnyCancellable, когда издатель закончит

Есть ли хороший способ обработать массив AnyCancellable, чтобы удалить сохраненный AnyCancellable, когда он закончен / отменен?

Скажи, что у меня есть это

import Combine
import Foundation

class Foo {

    private var cancellables = [AnyCancellable]()

    func startSomeTask() -> Future<Void, Never> {
        Future<Void, Never> { promise in
            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
                promise(.success(()))
            }
        }
    }

    func taskCaller() {
        startSomeTask()
            .sink { print("Do your stuff") }
            .store(in: &cancellables)
    }

}

Каждый раз, когда вызывается taskCaller, создается и сохраняется в массиве AnyCancellable. Я хотел бы удалить этот экземпляр из массива после его завершения, чтобы избежать потери памяти.

Я знаю, что могу сделать что-то подобное вместо массива

var taskCancellable: AnyCancellable?

И сохраните отменяемое, выполнив:

taskCancellable = startSomeTask().sink { print("Do your stuff") }

Но это приведет к созданию нескольких одиночных отменяемых элементов и может загрязнить код. Я не хочу такого класса

class Bar {

    private var task1: AnyCancellable?
    private var task2: AnyCancellable?
    private var task3: AnyCancellable?
    private var task4: AnyCancellable?
    private var task5: AnyCancellable?
    private var task6: AnyCancellable?

}

person Rico Crescenzio    schedule 14.03.2020    source источник


Ответы (2)


Хорошая идея, но убрать действительно нечего. Когда завершение (завершение или отмена) переходит в конвейер, все вверх по конвейеру отменяется в надлежащем порядке, все классы (объекты подписки) освобождаются и т. Д. Таким образом, единственное, что остается значимо «живым» после того, как ваше будущее сгенерировало значение или отказ, - это приемник в конце конвейера, и он крошечный.

Чтобы в этом убедиться, запустите этот код

for _ in 1...100 {
    self.taskCaller()
}

и используйте Инструменты, чтобы отслеживать свои распределения. Разумеется, после этого появляется 100 объектов AnyCancellable на общую сумму 3 КБ. Фьючерсов нет; ни один из других объектов, созданных в startSomeTask, до сих пор не существует, и они настолько крошечные (48 байт), что не имело бы значения, если бы они существовали.

person matt    schedule 14.03.2020
comment
Вы думаете, я просто слишком много думаю? - person Rico Crescenzio; 14.03.2020
comment
Я собирался сделать то же самое, спасибо, думаю, я просто проигнорирую это. - person Rico Crescenzio; 14.03.2020
comment
Преждевременная оптимизация - корень всех зол. :) - person matt; 14.03.2020
comment
Этот ответ дает плохой совет, а именно, поскольку раковина крошечная, можно позволить им накапливаться. Это утечка памяти. В лучшем случае это запутает попытки найти другие утечки, а в худшем - потребует чрезмерного количества памяти. И это может быть хорошо, когда вы впервые напишете это, но последующие изменения могут вас укусить. Лучше убраться, когда сможешь. Я бы хотел, чтобы Combine упростил эту задачу. - person Rick; 04.07.2021
comment
@Rick Да, я согласен, он не может решить проблему. Другой ответ мне нравится больше, чем мой. - person matt; 04.07.2021

Я задал себе тот же вопрос, работая над приложением, которое генерирует большое количество отменяемых элементов, которые в конечном итоге хранятся в одном массиве. А для долгоживущих приложений размер массива может стать огромным.

Даже если объем памяти невелик, это все равно объекты, которые потребляют кучу, что со временем может привести к ее фрагментации.

Решение, которое я нашел, - удалить отменяемое, когда издатель закончит:

func consumePublisher() {
    var cancellable: AnyCancellable!
    cancellable = makePublisher()
        .sink(receiveCompletion: { [weak self] _ in self?.cancellables.remove(cancellable) },
              receiveValue: { doSomeWork() })
    cancellable.store(in: &cancellables)
}

Действительно, код не такой уж и красивый, но, по крайней мере, нет траты памяти :)

Некоторые функции высокого порядка могут использоваться для повторного использования этого шаблона в других местах того же класса:

func cleanupCompletion<T>(_ cancellable: AnyCancellable) -> (Subscribers.Completion<T>) -> Void {
    return { [weak self] _ in self?.cancellables.remove(cancellable) }
}

func consumePublisher() {
    var cancellable: AnyCancellable!
    cancellable = makePublisher()
        .sink(receiveCompletion: cleanupCompletion(cancellable),
              receiveValue: { doSomeWork() })
    cancellable.store(in: &cancellables)
}

Или, если вам нужна поддержка для завершения работы:

func cleanupCompletion<T>(_ cancellable: AnyCancellable) -> (Subscribers.Completion<T>) -> Void {
        return { [weak self] _ in self?.cancellables.remove(cancellable) }
    }
    
func cleanupCompletion<T>(_ cancellable: AnyCancellable, completionWorker: @escaping (Subscribers.Completion<T>) -> Void) -> (Subscribers.Completion<T>) -> Void {
    return { [weak self] in
        self?.cancellables.remove(cancellable)
       completionWorker($0)
    }
}

func consumePublisher() {
    var cancellable: AnyCancellable!
    cancellable = makePublisher()
        .sink(receiveCompletion: cleanupCompletion(cancellable) { doCompletionWork() },
              receiveValue: { doSomeWork() })
    cancellable.store(in: &cancellables)
}
person Cristik    schedule 18.02.2021