Как управлять жизненным циклом подписок в Combine
Что проверяют
Понимаете ли вы, что подписка в Combine живёт ровно столько, сколько живёт её AnyCancellable, и умеете ли не создавать при этом циклы удержания. Следом часто спрашивают, почему подписка «сразу умерла» или экран не освобождается после закрытия.
Как отвечать
Каркас:
AnyCancellable: его возвращаютsinkиassign(to:on:), а вdeinitон вызываетcancel(). Не сохранили результат, и асинхронная подписка отменится сразу, ответ сети доsinkне дойдёт;- хранение:
store(in: &cancellables)вSet<AnyCancellable>у владельца. Освобождается владелец, вместе с ним уходят и подписки. Для одной заменяемой подписки удобнее свойствоAnyCancellable?: присвоение нового значения отменяет старое; - циклы удержания: владелец держит
cancellables, тот держит замыкание, замыкание держитself. Поэтому вsinkпишут[weak self].assign(to: \.title, on: self)держитselfсильно и создаёт тот же цикл. Для@Publishedестьassign(to: &$title): он ничего не возвращает и живёт вместе со свойством; - завершение и отмена: после
.finishedили.failureподписка заканчивается сама.cancel()completion не присылает, отследить отмену можно черезhandleEvents(receiveCancel:); - ошибка завершает поток:
.failureво внутреннем издателеflatMapобрывает всю цепочку. Чтобы подписка пережила неудачный запрос,catchилиreplaceErrorставят внутриflatMap; - мост в async/await:
publisher.valuesотдаётAsyncSequence, и тогда подписка отменяется вместе сTask, в котором её перебирают.
В новом коде на SwiftUI состояние всё чаще строят на @Observable, а Combine остаётся для потоков событий.
Где спотыкаются
Не сохраняют AnyCancellable, например подписываются в локальной функции. С Just всё работает, потому что значение приходит синхронно, а с сетевым запросом ничего не происходит.
Используют assign(to:on:) с self или сильный self в sink. Контроллер или вью-модель не освобождаются, и подписки продолжают срабатывать после закрытия экрана.
Отменяют подписку через cancellables.first?.cancel(). Set не упорядочен, отменится случайная подписка, и она к тому же останется в наборе.