Swinject is een DI-container voor Swift die het Dependency Injection-patroon implementeert in iOS-applicaties. Het framework automatiseert het creëren en injecteren van afhankelijkheden en elimineert handmatig beheer van objecten en fabrieken. Volgens Swinject op GitHub ondersteunt de bibliotheek Constructor Injection, Property Injection en Method Injection met een flexibel systeem van scopes voor levensduurbeheer.
Belangrijkste punten
Swinject is een open-source DI-container voor de programmeertaal Swift, ontworpen om afhankelijkheidsinjectie in applicaties voor iOS, macOS en watchOS te vereenvoudigen. Het framework gebruikt de Service Locator-aanpak: services worden geregistreerd in een centrale container en de container lost automatisch de afhankelijkheidsgraaf op bij een instantie-aanvraag.
Dependency Injection (DI) is een ontwerppatroon waarbij het object zijn afhankelijkheden van buitenaf ontvangt in plaats van ze intern te creëren. Dit vermindert de koppeling tussen componenten, vereenvoudigt modulair testen en maakt het mogelijk implementaties te vervangen zonder de consumentcode te wijzigen.
Volgens Martin Fowler (2004) is DI een speciaal geval van Inversion of Control en wordt geïmplementeerd via injectie via constructor, eigenschap of methode. Swinject automatiseert dit proces en elimineert het handmatig schrijven van fabrieken en servicelocators.
Pas Swinject toe in projecten met drie of meer services met kruisafhankelijkheden, waar handmatige objectconstructie leidt tot groei van initialisatiecode en verminderde testbaarheid.
Swinject integreert nauw met het Apple-ecosysteem en ondersteunt alle versies van Swift vanaf 3.0. Het framework is compatibel met Objective-C via bruggen, waardoor het kan worden ingezet in bestaande projecten geschreven in een gemengde taal zonder volledige codemigratie. Dit is vooral relevant voor grote applicaties met een ontwikkelingsgeschiedenis van meer dan vijf jaar.
De Swinject-container is geïmplementeerd door de klasse Container, die het register van geregistreerde services bewaart. Bij aanroep van de methode resolve creëert de container een object en lost al zijn afhankelijkheden recursief op volgens de registratiegraaf.
Container is het centrale object waarin overeenkomsten tussen abstractie en implementatie worden geregistreerd. Service is een protocol dat het contract definieert, en Component is een klasse die dit protocol implementeert. Registratie gebeurt via de methode register, die het servicetype en de fabriek accepteert.
let container = Container()
container.register(Networking.self) { _ in
NetworkService()
}
let service = container.resolve(Networking.self)
De methode resolve retourneert een instantie van de concrete implementatie geregistreerd voor het opgegeven protocol. Als de afhankelijkheid niet is geregistreerd, gooit de container een fatale fout voor snelle detectie van het probleem in de ontwikkelingsfase.
Elke registratie creëert een vermelding met een fabriekfunctie en een geselecteerde scope. Een service kan meerdere registraties met verschillende namen hebben, waardoor een specifieke implementatie op naam kan worden gekozen — handig voor verschillende omgevingen (ontwikkeling, staging, productie).
Het oplossingsproces van afhankelijkheden (resolution) werkt recursief: wanneer de container een Component-instantie creëert, analyseert hij de initialisator en roept voor elke parameter resolve van het corresponderende type aan. Als de afhankelijkheid ook eigen afhankelijkheden heeft, gaat het proces door tot de hele graaf volledig is opgebouwd. De nestdiepte is alleen beperkt door het beschikbare geheugen, maar overschrijdt in de praktijk zelden vijf niveaus.
Swinject ondersteunt drie belangrijke manieren van afhankelijkheidsinjectie, elk toepasbaar afhankelijk van de architecturale context.
Constructor Injection — injectie van afhankelijkheden via initialisatieparameters. Dit is de voorkeursmanier, die garandeert dat het object altijd in een correcte staat verkeert vanaf het moment van creatie. Swinject lost automatisch alle aan de constructor doorgegeven afhankelijkheden op.
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 — injectie door het instellen van eigenschappen van het object na initialisatie. Gebruikt wanneer de afhankelijkheid optioneel is of niet via de constructor kan worden doorgegeven, bijvoorbeeld bij het werken met Storyboard, waar de view controller automatisch wordt aangemaakt. Swinject ondersteunt de @Inject-annotatie voor automatische injectie van eigenschappen via runtime zonder expliciete resolve-aanroep.
Bij gebruik van Property Injection is het belangrijk ervoor te zorgen dat de afhankelijkheid is ingesteld vóór de eerste toegang tot het object. Anders blijft de eigenschap nil, wat leidt tot een onverwachte crash. Swinject lost dit probleem op via het Implicitly Unwrapped Optional-mechanisme en strikte verificatie in de fase van het oplossen van de afhankelijkheidsgraaf.
Method Injection — injectie via methodeparameters. Toegepast voor services die alleen nodig zijn voor het uitvoeren van één bewerking en niet als permanente objectstatus moeten worden bewaard. Dit is de minst voorkomende, maar voor callbacks nuttige injectiemethode.
ObjectScope — het mechanisme dat de levensduur van een gemaakte instantie binnen de Swinject-container bepaalt. Het framework biedt drie ingebouwde scopes met de mogelijkheid om aangepaste scopes te maken via het protocol ObjectScopeProtocol.
De graph-scope — standaardwaarde. Bij elke resolve-aanroep wordt een nieuwe instantie gemaakt die alleen leeft voor de duur van het oplossen van de afhankelijkheidsgraaf. Dit is een veilige keuze voor stateless services omdat het geheugenlekken door caching elimineert.
De container-scope — singleton binnen de container. De instantie wordt één keer gemaakt bij de eerste resolve en teruggegeven bij alle volgende aanvragen. Geschikt voor services met gedeelde status: gegevenscache, logger, applicatie-instellingen.
De transient-scope — elke resolve-aanroep maakt een nieuwe instantie zonder caching. Gebruikt voor lichte objecten die niet opnieuw hoeven te worden gebruikt — bijvoorbeeld voor modules die met een specifiek HTTP-verzoek werken.
| Scope | Levensduur | Aanbevolen gebruik |
|---|---|---|
| graph | Voor duur van graafoplossing | Stateless services standaard |
| container | Hele levensduur van container | Singleton: cache, logger, netwerkclient |
| transient | Zonder caching | Lichte objecten voor eenmalig gebruik |
Integratie van Swinject in een echt iOS-project begint met initialisatie van de container bij het opstarten van de applicatie — in AppDelegate of scène. Het wordt aanbevolen om registraties te structureren via Assembly: een aparte klasse of structuur die gerelateerde services groepeert.
Volgens een enquête van de Swift Developer Community (2025) gebruikt 43% van de iOS-ontwikkelaars DI-containers in commerciële projecten voor het beheer van afhankelijkheden van de netwerklaag, repositories en navigatiecoördinatoren. Swinject blijft de populairste oplossing dankzij minimale syntax en compatibiliteit met Objective-C.
Storyboard Injection — een unieke mogelijkheid van Swinject: de container injecteert automatisch afhankelijkheden in view controllers die vanuit Storyboard worden gemaakt, zonder extra code in AppDelegate. Hiervoor wordt een speciale resolver gebruikt die aan UIStoryboard wordt doorgegeven via de methode init(container:), die het aanmaken van de view controller onderschept en de geregistreerde afhankelijkheden injecteert.
In grote projecten kan Swinject worden gecombineerd met navigatiecoördinatoren: de coördinator ontvangt de container en maakt schermen aan, waarbij hun afhankelijkheden via resolve worden opgelost, wat een enkel configuratiepunt voor de hele scène behoudt.
Architectuur met Assembly — het aanbevolen patroon voor het organiseren van registraties. Elke Assembly groepeert gerelateerde services (bijv. NetworkingAssembly, DatabaseAssembly) en kan afhankelijk zijn van andere Assemblies. Bij initialisatie van de container worden alle Assemblies geladen en registreren ze hun services, wat zorgt voor een duidelijke scheiding van verantwoordelijkheden en navigatie door de DI-configuratie in grote projecten met tientallen services vereenvoudigt.
Voor het debuggen van de DI-graaf biedt Swinject de extensie SwinjectPropertyLoader, die configuratie laadt uit een plist-bestand, en SwinjectStoryboard — integratie met storyboards via een speciale versie van UIStoryboard. Deze tools zijn vooral nuttig in de fase van het overzetten van een bestaand project van handmatige objectconstructie naar DI: de ontwikkelaar kan geleidelijk services registreren, de afhankelijkheidsgraaf controleren via tests en logging van resolutiefouten, zonder de ontwikkeling van de belangrijkste functies van de applicatie te stoppen.
Swinject biedt ook integratie met RxSwift en Combine via de extensie SwinjectAutoregistration voor automatische resolutie van afhankelijkheden op basis van initialisatieparametertypes zonder expliciete registratie van fabrieken. Dit vermindert de hoeveelheid registratiecode voor eenvoudige services: het volstaat om container.register(ServiceProtocol.self) aan te roepen zonder een fabriek op te geven, en Swinject bouwt zelfstandig de fabriek op basis van Signal-reflectie geleverd door de Swift-runtime. Deze aanpak wordt aanbevolen voor services waarvan de constructor alleen basistypen accepteert en geen complexe logica vereist bij het aanmaken.
Veelgestelde vragen
Swinject is geschreven in puur Swift zonder codegeneratie en reflectie. In tegenstelling tot Needle vereist het geen generatie van broncode, en vergeleken met Dip biedt het ingebouwde ondersteuning voor Storyboard Injection, wat integratie in bestaande UIKit-projecten vereenvoudigt.
Voeg het pakket toe via URL github.com/Swinject/Swinject via Xcode in het menu File — Add Packages. Installatie via CocoaPods en Carthage is ook beschikbaar. Na installatie importeert u de Swinject-module en maakt u een Container-instantie.
Ja, Swinject is volledig compatibel met SwiftUI. Afhankelijkheden worden geïnjecteerd via View-initialisatoren of via Environment, waar de container wordt doorgegeven als EnvironmentObject. Swinject is niet afhankelijk van UIKit en werkt hetzelfde met beide frameworks.
Maak een aparte container voor tests en vervang echte services door mocks. Swinject maakt het mogelijk registraties te overschrijven zonder de consumentcode te wijzigen. Elke test krijgt een geïsoleerde container met een minimale set afhankelijkheden.
Gebruik voor analytics de container-scope, zodat alle schermen gebeurtenissen via één instantie verzenden. Dit garandeert een uniforme verzendwachtrij en correcte batch-aggregatie zonder gegevensduplicatie tussen verschillende consumenten.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook