Где хранятся подписки в Combine
Что проверяют
Понимаете ли вы, что время жизни подписки в Combine задаёт объект, который вы держите, а не сам паблишер. Следом обычно спрашивают про утечки: откуда берётся цикл ссылок и чем опасен assign(to:on:).
Как отвечать
Операторы sink и assign(to:on:) возвращают AnyCancellable. Это обёртка, которая при освобождении вызывает cancel(). Отсюда правило: подписка живёт ровно столько, сколько живёт этот объект. Где его хранить, решаете вы:
Set<AnyCancellable>в свойстве владельца: обычно во ViewModel, через.store(in: &cancellables). Когда владелец освобождается, освобождается и набор, все подписки отменяются;- отдельное свойство
AnyCancellable?: удобно, когда подписка одна и её нужно заменять, например на поиск. Присвоили новую, старая освободилась и отменилась; assign(to: &$property): вариант для@Published, появился в iOS 14. Он не возвращаетAnyCancellable, подписка привязана к жизни самого свойства и не создаёт сильной ссылки на владельца.
Сама подписка, объект с протоколом Subscription, живёт внутри цепочки: подписчик и подписка держат друг друга, пока цепочку не отменят или паблишер не завершится. AnyCancellable только даёт вам рычаг, чтобы эту связь разорвать.
Можно добавить современный контекст: с iOS 15 паблишер можно читать через for await value in publisher.values, и тогда время жизни подписки совпадает с Task, а в SwiftUI с iOS 17 многие сценарии закрывает @Observable без Combine.
Где спотыкаются
Не сохраняют результат sink. Объект сразу освобождается, и подписка отменяется, не получив ни одного значения. Синхронные паблишеры вроде Just успевают отдать значение до отмены, поэтому в простом примере ошибка не видна.
Захватывают self в замыкании sink сильно или пишут assign(to: \.value, on: self). Владелец держит набор подписок, подписка держит владельца, получается цикл, и ViewModel не освобождается никогда. Нужен [weak self] или assign(to: &$value).
Подписываются заново при каждой настройке ячейки таблицы в один и тот же набор. Подписки копятся, обработчики срабатывают по несколько раз, поэтому набор нужно очищать в prepareForReuse.