Swinject — це DI-контейнер для Swift, який реалізує патерн Dependency Injection в iOS-додатках. Фреймворк автоматизує створення та впровадження залежностей, позбавляючи від ручного керування об'єктами та фабриками. За даними Swinject на GitHub, бібліотека підтримує Constructor Injection, Property Injection та Method Injection з гнучкою системою скопів для керування часом життя.
Головне
Swinject — це відкритий DI-контейнер для мови Swift, розроблений для спрощення впровадження залежностей у додатках під iOS, macOS та watchOS. Фреймворк використовує підхід Service Locator: сервіси реєструються в центральному контейнері, а контейнер автоматично вирішує граф залежностей під час запиту екземпляра.
Dependency Injection (DI) — патерн проєктування, при якому об'єкт отримує свої залежності ззовні, а не створює їх всередині. Це знижує зв'язність між компонентами, спрощує модульне тестування та дозволяє замінювати реалізації без зміни коду споживача.
За даними Martin Fowler (2004), DI є окремим випадком Inversion of Control і реалізується через впровадження через конструктор, властивість або метод. Swinject автоматизує цей процес, позбавляючи від ручного написання фабрик і сервіс-локаторів.
Застосовуйте Swinject у проєктах із трьома та більше сервісами, що мають перехресні залежності, де ручне конструювання об'єктів веде до розростання ініціалізаційного коду та зниження тестованості.
Swinject тісно інтегрується з екосистемою Apple та підтримує всі версії Swift, починаючи з 3.0. Фреймворк сумісний з Objective-C через містки, що дозволяє впроваджувати його в існуючі проєкти, написані змішаною мовою, без повної міграції коду. Це особливо актуально для великих додатків з історією розробки понад п'ять років.
Контейнер Swinject реалізований класом Container, який зберігає реєстр зареєстрованих сервісів. При зверненні до методу resolve контейнер створює об'єкт, вирішуючи всі його залежності рекурсивно за графом реєстрацій.
Container — центральний об'єкт, у який реєструються відповідності між абстракцією та її реалізацією. Service — це протокол, що визначає контракт, а Component — клас, що реалізує цей протокол. Реєстрація виконується методом register, який приймає тип сервісу та фабрику.
let container = Container()
container.register(Networking.self) { _ in
NetworkService()
}
let service = container.resolve(Networking.self)
Метод resolve повертає екземпляр конкретної реалізації, зареєстрованої для вказаного протоколу. Якщо залежність не зареєстрована, контейнер викидає фатальну помилку для швидкого виявлення проблеми на етапі розробки.
Кожна реєстрація створює запис із фабричною функцією та вибраним скопом. Один сервіс може мати кілька реєстрацій з різними іменами, що дозволяє вибирати конкретну реалізацію за іменем — корисно для різних середовищ (розробка, стейджинг, продакшн).
Процес вирішення залежності (resolution) працює рекурсивно: коли контейнер створює екземпляр Component, він аналізує його ініціалізатор і для кожного параметра викликає resolve відповідного типу. Якщо залежність також має свої залежності, процес триває доти, доки весь граф не буде повністю побудований. Глибина вкладеності обмежена лише доступною пам'яттю, але на практиці рідко перевищує п'ять рівнів.
Swinject підтримує три основні способи впровадження залежностей, кожен з яких застосовний залежно від архітектурного контексту.
Constructor Injection — впровадження залежностей через параметри ініціалізатора. Це найкращий спосіб, який гарантує, що об'єкт завжди знаходиться в коректному стані з моменту створення. Swinject автоматично вирішує всі залежності, передані в конструктор.
class LoginViewModel {
private let authService: AuthProtocol
init(authService: AuthProtocol) {
self.authService = authService
}
}
container.register(AuthProtocol.self) { _ in
AuthService()
}
container.register(LoginViewModel.self) { r in
LoginViewModel(authService: r.resolve(AuthProtocol.self)!)
}
Property Injection — впровадження через встановлення властивостей об'єкта після його ініціалізації. Використовується, коли залежність опціональна або не може бути передана через конструктор, наприклад, при роботі з Storyboard, де view controller створюється автоматично. Swinject підтримує анотацію @Inject для автоматичного впровадження властивостей через runtime без явного виклику resolve.
При використанні Property Injection важливо переконатися, що залежність встановлена до першого звернення до об'єкта. В іншому випадку властивість залишиться nil, що призведе до неочікуваного крашу. Swinject вирішує цю проблему через механізм Implicitly Unwrapped Optional і строгу перевірку на етапі вирішення графа залежностей.
Method Injection — впровадження через параметри методу. Застосовується для сервісів, які потрібні лише для виконання однієї операції і не повинні зберігатися як постійний стан об'єкта. Це найменш поширений, але корисний для колбеків спосіб впровадження.
ObjectScope — механізм, що визначає час життя створюваного екземпляра всередині контейнера Swinject. Фреймворк надає три вбудованих скопи з можливістю створення кастомних через протокол ObjectScopeProtocol.
Скоп graph — значення за замовчуванням. При кожному виклику resolve створюється новий екземпляр, який живе лише на час вирішення графа залежностей. Це безпечний вибір для сервісів без стану, оскільки виключає витоки пам'яті через кешування.
Скоп container — синглтон у рамках контейнера. Екземпляр створюється один раз при першому resolve і повертається при всіх наступних запитах. Підходить для сервісів із спільним станом: кеш даних, логер, налаштування додатка.
Скоп transient — кожен виклик resolve створює новий екземпляр без кешування. Використовується для легких об'єктів, які не потрібно перевикористовувати, — наприклад, для модулів роботи з конкретним HTTP-запитом.
| Скоп | Час життя | Рекомендоване застосування |
|---|---|---|
| graph | На час вирішення графа | Stateless-сервіси за замовчуванням |
| container | Весь час життя контейнера | Синглтони: кеш, логер, мережевий клієнт |
| transient | Без кешування | Легкі об'єкти для одноразового використання |
Інтеграція Swinject у реальний iOS-проєкт починається з ініціалізації контейнера на старті додатка — в AppDelegate або сцені. Рекомендується структурувати реєстрації через Assembly: окремий клас або структура, що групує пов'язані сервіси.
За даними опитування Swift Developer Community (2025), 43% iOS-розробників використовують DI-контейнери в комерційних проєктах для керування залежностями мережевого шару, репозиторіїв та координаторів навігації. Swinject залишається найбільш популярним рішенням завдяки мінімальному синтаксису та сумісності з Objective-C.
Storyboard Injection — унікальна можливість Swinject: контейнер автоматично впроваджує залежності у view controllerи, створювані з Storyboard, без додаткового коду в AppDelegate. Для цього використовується спеціальний resolver, що передається в UIStoryboard через метод init(container:), який перехоплює створення view controller та впроваджує зареєстровані залежності.
У великих проєктах Swinject можна комбінувати з координаторами навігації: координатор отримує контейнер і створює екрани, вирішуючи їхні залежності через resolve, що зберігає єдину точку конфігурації для всієї сцени.
Архітектура з Assembly — рекомендований патерн для організації реєстрацій. Кожен Assembly групує пов'язані сервіси (наприклад, NetworkingAssembly, DatabaseAssembly) і може залежати від інших Assembly. При ініціалізації контейнера всі Assembly завантажуються та реєструють свої сервіси, що дає чіткий розподіл відповідальності та спрощує навігацію по DI-конфігурації у великих проєктах із десятками сервісів.
Для налагодження DI-графа Swinject надає розширення SwinjectPropertyLoader, яке завантажує конфігурацію з plist-файлу, і SwinjectStoryboard — інтеграцію зі сторібордами через спеціальну версію UIStoryboard. Ці інструменти особливо корисні на етапі переведення існуючого проєкту з ручного конструювання об'єктів на DI: розробник може поступово реєструвати сервіси, перевіряючи граф залежностей через тести та логування помилок вирішення, не зупиняючи розробку основних фіч додатка.
Swinject також надає інтеграцію з RxSwift та Combine через розширення SwinjectAutoregistration для автоматичного вирішення залежностей за типами параметрів ініціалізатора без явної реєстрації фабрик. Це скорочує обсяг реєстраційного коду для простих сервісів: достатньо викликати container.register(ServiceProtocol.self) без вказання фабрики, і Swinject самостійно побудує фабрику на основі рефлексії Signal, що надається середовищем виконання Swift. Даний підхід рекомендується для сервісів, конструктор яких приймає лише базові типи і не потребує складної логіки при створенні.
Часто задавані питання
Swinject написаний на чистому Swift без кодогенерації та рефлексії. На відміну від Needle, не вимагає генерації вихідників, а порівняно з Dip — надає вбудовану підтримку Storyboard Injection, що спрощує інтеграцію в існуючі UIKit-проєкти.
Додайте пакет за URL github.com/Swinject/Swinject через Xcode в меню File — Add Packages. Також доступне встановлення через CocoaPods та Carthage. Після встановлення імпортуйте модуль Swinject та створіть екземпляр Container.
Так, Swinject повністю сумісний зі SwiftUI. Залежності впроваджуються через ініціалізатори View або через Environment, де контейнер передається як EnvironmentObject. Swinject не залежить від UIKit і однаково працює з обома фреймворками.
Створіть окремий контейнер для тестів, замінивши реальні сервіси моками. Swinject дозволяє перевизначати реєстрації, не змінюючи код споживачів. Кожен тест отримує ізольований контейнер з мінімальним набором залежностей.
Для аналітики використовуйте container-скоп, щоб всі екрани надсилали події через один екземпляр. Це гарантує єдину чергу відправки та коректну роботу batch-агрегації без дублювання даних між різними споживачами.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також