Как устроена коммуникация между parent и child моделями и используется ли dependency injection?
Что проверяют
Умеете ли вы связать экраны и их модели так, чтобы не было утечек памяти и скрытых зависимостей. Под parent и child обычно имеют в виду родительскую и дочернюю ViewModel, но иногда речь о Core Data с родительским и дочерним контекстом, уточните. Следом почти всегда спрашивают, где создаются зависимости и как вы подменяете их в тестах.
Как отвечать
Начните с направления связей: родитель знает о дочерней модели, дочерняя о родителе не знает. Родитель создаёт child и передаёт ему данные и зависимости, а обратная связь идёт через абстракцию:
- делегат: протокол и
weak var delegate, классика UIKit; - замыкания:
onSelect: (Item) -> Void, внутри обязательно[weak self]; - Combine или async: child публикует события через
PassthroughSubjectилиAsyncStream, родитель подписывается; - SwiftUI:
@Bindingдля общего значения, модели на@Observableначиная с iOS 17 илиObservableObjectна старых версиях.
DI в iOS это прежде всего внедрение через инициализатор: init(service: OrdersServiceProtocol). Зависимость объявлена протоколом, в тестах вместо неё мок. Сборку графа держат в одном месте: координатор, фабрика модуля или контейнер вроде Swinject или Factory. В SwiftUI часть зависимостей удобно раздавать через @Environment.
Если речь о Core Data: дочерний NSManagedObjectContext с заданным parent при save() отдаёт изменения в родительский контекст, а в хранилище они попадают только после save() у родителя. Так делают черновики и фоновый импорт.
Где спотыкаются
Дают child сильную ссылку на родителя или захватывают self в замыкании без weak. Получается цикл удержания, и экран не освобождается после закрытия.
Выдают за DI синглтон ServiceLocator.shared, который дёргают из любой модели. Зависимость становится скрытой, и подменить её в тесте уже нельзя.
Позволяют дочерней модели менять состояние родителя напрямую. Через месяц никто не может сказать, откуда пришло изменение, а тест родителя приходится писать вместе со всеми детьми.