Swinject è un container DI per Swift che implementa il pattern Dependency Injection nelle applicazioni iOS. Il framework automatizza la creazione e l'iniezione di dipendenze, eliminando la gestione manuale di oggetti e factory. Secondo Swinject su GitHub, la libreria supporta Constructor Injection, Property Injection e Method Injection con un flessibile sistema di scope per la gestione del ciclo di vita.
Punti chiave
Swinject è un container DI open source per il linguaggio Swift, progettato per semplificare l'iniezione di dipendenze nelle applicazioni per iOS, macOS e watchOS. Il framework utilizza l'approccio Service Locator: i servizi vengono registrati in un container centrale e il container risolve automaticamente il grafo delle dipendenze quando viene richiesta un'istanza.
Dependency Injection (DI) è un pattern di progettazione in cui un oggetto riceve le sue dipendenze dall'esterno invece di crearle internamente. Ciò riduce l'accoppiamento tra i componenti, semplifica i test unitari e consente di sostituire le implementazioni senza modificare il codice del consumatore.
Secondo Martin Fowler (2004), DI è un caso specifico di Inversion of Control e viene implementato tramite iniezione tramite costruttore, proprietà o metodo. Swinject automatizza questo processo, eliminando la necessità di scrivere manualmente factory e service locator.
Utilizza Swinject in progetti con tre o più servizi che hanno dipendenze incrociate, dove la costruzione manuale di oggetti porta a codice di inizializzazione gonfio e ridotta testabilità.
Swinject si integra strettamente con l'ecosistema Apple e supporta tutte le versioni di Swift a partire dalla 3.0. Il framework è compatibile con Objective-C tramite bridge, consentendo di introdurlo in progetti esistenti in linguaggio misto senza una migrazione completa del codice. Ciò è particolarmente rilevante per applicazioni grandi con più di cinque anni di sviluppo.
Il container Swinject è implementato dalla classe Container, che memorizza un registro dei servizi registrati. Quando viene chiamato il metodo resolve, il container crea un oggetto, risolvendo ricorsivamente tutte le sue dipendenze attraverso il grafo di registrazione.
Container è l'oggetto centrale in cui vengono registrate le corrispondenze tra astrazioni e loro implementazioni. Un Service è un protocollo che definisce un contratto, mentre un Component è una classe che implementa questo protocollo. La registrazione viene effettuata utilizzando il metodo register, che accetta il tipo di servizio e una factory.
let container = Container()
container.register(Networking.self) { _ in
NetworkService()
}
let service = container.resolve(Networking.self)
Il metodo resolve restituisce un'istanza dell'implementazione concreta registrata per il protocollo specificato. Se una dipendenza non è registrata, il container lancia un errore fatale per il rapido rilevamento del problema durante lo sviluppo.
Ogni registrazione crea una voce con una funzione factory e uno scope selezionato. Un singolo servizio può avere più registrazioni con nomi diversi, consentendo di selezionare un'implementazione specifica per nome — utile per diversi ambienti (sviluppo, staging, produzione).
Il processo di risoluzione delle dipendenze funziona in modo ricorsivo: quando il container crea un'istanza di Component, analizza il suo inizializzatore e per ogni parametro chiama resolve con il tipo corrispondente. Se una dipendenza ha anche le proprie dipendenze, il processo continua fino a quando l'intero grafo non è completamente costruito. La profondità di annidamento è limitata solo dalla memoria disponibile, ma in pratica raramente supera i cinque livelli.
Swinject supporta tre metodi principali di iniezione delle dipendenze, ciascuno applicabile a seconda del contesto architetturale.
Constructor Injection inietta le dipendenze tramite i parametri dell'inizializzatore. Questo è il metodo preferito, che garantisce che un oggetto sia sempre in uno stato valido dal momento della creazione. Swinject risolve automaticamente tutte le dipendenze passate al costruttore.
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 inietta le dipendenze impostando le proprietà dell'oggetto dopo l'inizializzazione. Viene utilizzato quando una dipendenza è opzionale o non può essere passata tramite il costruttore, ad esempio, quando si lavora con Storyboard, dove il view controller viene creato automaticamente. Swinject supporta l'annotazione @Inject per l'iniezione automatica delle proprietà in fase di esecuzione senza una chiamata esplicita a resolve.
Quando si utilizza Property Injection, è importante assicurarsi che la dipendenza sia impostata prima del primo accesso all'oggetto. Altrimenti, la proprietà rimarrà nil, portando a un crash imprevisto. Swinject risolve questo problema attraverso il meccanismo Implicitly Unwrapped Optional e una validazione rigorosa nella fase di risoluzione del grafo delle dipendenze.
Method Injection inietta le dipendenze tramite i parametri del metodo. Viene utilizzato per servizi necessari solo per eseguire una singola operazione e che non devono essere memorizzati come stato permanente dell'oggetto. Questo è il metodo meno comune ma utile per i callback.
ObjectScope è un meccanismo che determina la durata di un'istanza creata all'interno del container Swinject. Il framework fornisce tre scope integrati con la possibilità di crearne di personalizzati tramite ObjectScopeProtocol.
Lo scope graph è il valore predefinito. Ogni chiamata a resolve crea una nuova istanza che vive solo per la durata della risoluzione del grafo delle dipendenze. È una scelta sicura per i servizi senza stato poiché elimina le perdite di memoria dovute alla memorizzazione nella cache.
Lo scope container è un singleton all'interno del container. L'istanza viene creata una volta al primo resolve e restituita per tutte le richieste successive. Adatto per servizi con stato condiviso: cache dati, logger, impostazioni dell'applicazione.
Lo scope transient crea una nuova istanza ad ogni chiamata a resolve senza memorizzazione nella cache. Utilizzato per oggetti leggeri che non necessitano di essere riutilizzati — ad esempio, moduli che gestiscono una specifica richiesta HTTP.
| Scope | Durata | Utilizzo consigliato |
|---|---|---|
| graph | Per la durata della risoluzione del grafo | Servizi senza stato per impostazione predefinita |
| container | Per tutta la vita del container | Singleton: cache, logger, client di rete |
| transient | Senza cache | Oggetti leggeri per uso singolo |
L'integrazione di Swinject in un progetto iOS reale inizia con l'inizializzazione del container all'avvio dell'applicazione — in AppDelegate o nella scena. Si consiglia di strutturare le registrazioni tramite Assembly: una classe o struttura separata che raggruppa i servizi correlati.
Secondo un sondaggio della Swift Developer Community (2025), il 43% degli sviluppatori iOS utilizza container DI in progetti commerciali per gestire le dipendenze del livello di rete, dei repository e dei coordinatori di navigazione. Swinject rimane la soluzione più popolare grazie alla sua sintassi minima e alla compatibilità con Objective-C.
Storyboard Injection è una caratteristica unica di Swinject: il container inietta automaticamente le dipendenze nei view controller creati da Storyboard senza codice aggiuntivo in AppDelegate. Utilizza un risolutore speciale passato a UIStoryboard tramite il metodo init(container:), che intercetta la creazione del view controller e inietta le dipendenze registrate.
Nei progetti grandi, Swinject può essere combinato con coordinatori di navigazione: il coordinatore riceve il container e crea schermate risolvendo le loro dipendenze tramite resolve, mantenendo un singolo punto di configurazione per l'intera scena.
L'architettura Assembly è il pattern consigliato per organizzare le registrazioni. Ogni Assembly raggruppa servizi correlati (ad esempio, NetworkingAssembly, DatabaseAssembly) e può dipendere da altri Assembly. Durante l'inizializzazione del container, tutti gli Assembly vengono caricati e registrano i propri servizi, fornendo una chiara separazione delle responsabilità e semplificando la navigazione attraverso la configurazione DI in progetti grandi con decine di servizi.
Per il debug del grafo DI, Swinject fornisce l'estensione SwinjectPropertyLoader, che carica la configurazione da un file plist, e SwinjectStoryboard — integrazione con gli storyboard tramite una versione speciale di UIStoryboard. Questi strumenti sono particolarmente utili durante la migrazione di un progetto esistente dalla costruzione manuale di oggetti a DI: lo sviluppatore può registrare gradualmente i servizi, verificando il grafo delle dipendenze tramite test e registrazione degli errori di risoluzione senza interrompere lo sviluppo delle funzionalità principali.
Swinject fornisce anche l'integrazione con RxSwift e Combine tramite l'estensione SwinjectAutoregistration per la risoluzione automatica delle dipendenze basata sui tipi di parametri dell'inizializzatore senza registrazione esplicita della factory. Ciò riduce la quantità di codice di registrazione per i servizi semplici: basta chiamare container.register(ServiceProtocol.self) senza specificare una factory e Swinject costruirà automaticamente la factory basandosi sulla riflessione Signal fornita dal runtime Swift. Questo approccio è consigliato per servizi i cui costruttori accettano solo tipi di base e non richiedono logica di creazione complessa.
Domande frequenti
Swinject è scritto in puro Swift senza generazione di codice o riflessione. A differenza di Needle, non richiede generazione di sorgenti e, rispetto a Dip, fornisce supporto integrato per Storyboard Injection, semplificando l'integrazione in progetti UIKit esistenti.
Aggiungi il pacchetto tramite URL github.com/Swinject/Swinject attraverso Xcode nel menu File — Add Packages. È disponibile anche l'installazione tramite CocoaPods e Carthage. Dopo l'installazione, importa il modulo Swinject e crea un'istanza di Container.
Sì, Swinject è completamente compatibile con SwiftUI. Le dipendenze vengono iniettate tramite gli inizializzatori di View o tramite Environment, dove il container viene passato come EnvironmentObject. Swinject non dipende da UIKit e funziona ugualmente bene con entrambi i framework.
Crea un container separato per i test, sostituendo i servizi reali con mock. Swinject consente di sovrascrivere le registrazioni senza modificare il codice dei consumatori. Ogni test riceve un container isolato con un insieme minimo di dipendenze.
Per le analisi, utilizza lo scope container in modo che tutti gli schermi inviino eventi attraverso una singola istanza. Ciò garantisce una coda di invio unificata e il corretto funzionamento dell'aggregazione batch senza duplicazione dei dati tra diversi consumatori.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche