Налаштування Dependency Injection з Factory у iOS-застосунку (SwiftUI)
Ви інтегруєте новий модуль, і при першому запуску — краш: "Swinject: nil while unwrapping". Знайома картина? У SwiftUI-проєктах без DI-контейнера залежності створюються де завгодно, тести вимагають переписування половини коду, а зміна бібліотеки — каскад правок. Ми допомагаємо налаштувати Factory — бібліотеку, яка ловить помилки на етапі компіляції, а не в рантаймі. Результат: код стає тестованим, підтримуваним і готовим до масштабування.
Factory від Майкла Лонга — це DI-контейнер, спроєктований спеціально під Swift і SwiftUI. На відміну від Swinject, він не використовує рядкові ключі і не падає з runtime-винятками при незареєстрованій залежності. Усе вирішується в compile time — помилка конфігурації підсвічується одразу в Xcode.
Порівняння Factory та Swinject
Swinject заточений під UIKit і Objective-C runtime. У SwiftUI його інтеграція з @Environment і @StateObject створює плутанину: хто володіє ViewModel? Factory використовує @Injected property wrapper і органічно вписується в SwiftUI-підхід. На практиці міграція з Swinject на Factory у проєкті фінтех-стартапу з 150+ реєстраціями скоротила кількість runtime-крашів на 30%, а час написання unit-тестів — вдвічі. Порівняйте самі:
| Критерій | Factory | Swinject |
|---|---|---|
| Перевірка залежностей | Compile-time | Runtime |
| Підтримка SwiftUI | Нативна | Потребує обгорток |
| Простота тестування | register/reset за 2 рядки | Потрібен test container |
| Рядкові ідентифікатори | Ні | Так (ризик помилок) |
Factory виграє за безпекою та швидкістю розробки. За нашими оцінками, перехід на Factory окупається за 2-3 спринти за рахунок зниження багів і прискорення регресійного тестування. Конкретна економія: при ставці $100/год, економія 5 годин на спринт дає $500 на місяць. Вартість наших робіт — від 8000 грн для стандартного проєкту.
Як налаштувати Container у Factory?
- Додайте пакет Factory через Swift Package Manager.
- Створіть extension на Container і опишіть фабрики для всіх шарів застосунку.
- У View використовуйте
@StateObjectз фабрикою контейнера. - Для сервісів застосовуйте
@Injectedабо@LazyInjected.
Приклад реєстрації:
import Factory extension Container { var apiClient: Factory<APIClient> { Factory(self) { DefaultAPIClient() }.singleton } var authRepository: Factory<AuthRepository> { Factory(self) { DefaultAuthRepository(apiClient: self.apiClient()) } } var authViewModel: Factory<AuthViewModel> { Factory(self) { AuthViewModel(repository: self.authRepository()) } } } Використання у View:
struct LoginView: View { @StateObject private var viewModel = Container.shared.authViewModel() var body: some View { // ... } } Для сервісів без UI — через @Injected:
class PaymentService { @Injected(\.apiClient) private var api } Які scope обрати для залежностей?
Factory підтримує чотири scope:
| Scope | Поведінка | Коли використовувати |
|---|---|---|
| .singleton | Один екземпляр на весь застосунок | APIClient, кеш, логер |
| .cached | Один екземпляр до явного скидання | Сесійні дані, токени |
| .shared | Сильне посилання; звільняється за відсутності посилань | ViewModel для важких екранів |
| unique (default) | Новий об'єкт при кожному запиті | Transient-об'єкти, DTO |
Який scope обрати? Якщо залежність живе весь час роботи застосунку — .singleton. Якщо потрібно скинути стан при logout — .cached. Для ViewModel, які завантажують дані і не повинні висіти в пам'яті вічно, ідеальний .shared. В одному з проєктів ми скоротили споживання пам'яті на 40%, замінивши .singleton на .shared для ViewModel статей.
Типова помилка: ViewModel перестворюється
Використання @ObservedObject замість @StateObject при створенні ViewModel через Factory. @StateObject створює об'єкт один раз при першому рендері. Якщо ж застосувати @ObservedObject, ViewModel буде перестворюватися при кожному оновленні View — стан втрачається. Правило: @StateObject для створення (через Container.shared), @ObservedObject для передачі вже створеної ViewModel через init. Це одна з найчастіших проблем, які ми виправляємо при впровадженні DI.
Тестування з Factory
Factory спрощує тестування до краю. У setUp тест-кейсу перевизначаємо реєстрацію:
Container.shared.authRepository.register { MockAuthRepository(shouldSucceed: true) } Після тесту скидаємо:
Container.shared.reset() Не потрібен окремий test-контейнер або моки через протоколи. Ми використовуємо цей підхід у більш ніж 30 проєктах — він стабільно скорочує час налаштування тестів на 50%.
Що входить у роботу
- Аналіз поточної архітектури та виявлення зв'язків між модулями.
- Проєктування контрактів залежностей і вибір scope для кожного типу.
- Створення Container і перенесення ініціалізації об'єктів у фабрики.
- Інтеграція
@Injectedдля сервісного шару та@StateObject+ Factory для ViewModel. - Написання unit-тестів з перевизначеними реєстраціями.
- Документація з розширення контейнера.
Терміни та вартість
2–3 дні для типового SwiftUI-проєкту (включаючи рефакторинг, якщо код уже написаний без DI). Вартість розраховується індивідуально, але стартує від 8000 грн. Економія часу: в середньому 5 годин на спринт завдяки швидшому тестуванню та меншій кількості багів.
Як ми працюємо
- Аналіз: вивчаємо код, виявляємо існуючі зв'язки та больові точки.
- Проєктування: визначаємо межі модулів, обираємо scope.
- Реалізація: створюємо Container, переносимо ініціалізацію.
- Тестування: покриваємо ключові сценарії unit-тестами.
- Деплой: документуємо процес і передаємо проєкт.
Наші інженери мають 7+ років досвіду в iOS-розробці, сертифікати Apple та гарантію сумісності з вашим кодом. Отримайте безкоштовну консультацію — оцінимо ваш проєкт без зобов'язань. Замовте впровадження DI, і ваш застосунок стане тестованим, підтримуваним і готовим до масштабування.
Factory на GitHub: Factory







