Contenedor de Inyección de Dependencias para Swift (iOS/macOS): Swinject
En el desarrollo moderno de iOS y macOS con Swift, los patrones arquitectónicos juegan un papel clave en la creación de código mantenible y comprobable. Una de las herramientas más potentes para implementar el principio de inversión de dependencias (Dependency Inversion Principle) es Swinject, un contenedor DI ligero pero funcional, escrito en Swift. Swinject permite gestionar de forma centralizada la creación de objetos y sus dependencias, liberando al desarrollador de la construcción manual del grafo de objetos.
¿Qué es la Inyección de Dependencias y por qué se necesita un contenedor?
La Inyección de Dependencias (DI) es una técnica en la que un objeto recibe sus dependencias desde el exterior, en lugar de crearlas por sí mismo. Esto reduce el acoplamiento del código y simplifica las pruebas unitarias. Sin embargo, en proyectos grandes, la inyección manual de dependencias genera un código voluminoso. Aquí es donde un contenedor DI resulta útil, ya que:
- Automatiza la creación y vinculación de objetos
- Gestiona el ciclo de vida de los objetos (singletons, nuevas instancias)
- Facilita el reemplazo de implementaciones (por ejemplo, para pruebas)
- Mejora la legibilidad y mantenibilidad del código
Instalación de Swinject
Swinject se puede instalar a través de Swift Package Manager, CocoaPods o Carthage. Para SPM, agregue en Package.swift:
dependencies: [ .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0")]O mediante CocoaPods:
pod 'Swinject', '~> 2.8'Conceptos principales de Swinject
La biblioteca se basa en varios componentes clave:
- Container — el registrador y resolvedor principal de dependencias
- Service — el tipo que se registra (generalmente un protocolo o clase)
- Component — la implementación concreta del servicio
- Scope — define el ciclo de vida del objeto (transient, graph, container, weak)
Ejemplo práctico: registro y resolución de dependencias
Consideremos un escenario típico: una aplicación con un servicio de autenticación y un controlador que lo utiliza.
// Protocolo del servicioprotocol AuthServiceProtocol { func login(username: String, password: String) -> Bool}
// Implementaciónclass AuthService: AuthServiceProtocol { func login(username: String, password: String) -> Bool { // Lógica de autenticación return username == "admin" && password == "1234" }}
// Controlador que depende de AuthServiceProtocolclass LoginViewController { private let authService: AuthServiceProtocol // Inyección a través del inicializador init(authService: AuthServiceProtocol) { self.authService = authService } func authenticate(user: String, pass: String) { if authService.login(username: user, password: pass) { print("Inicio de sesión exitoso") } else { print("Datos incorrectos") } }}
// Configuración del contenedorlet container = Container()container.register(AuthServiceProtocol.self) { _ in AuthService() }container.register(LoginViewController.self) { r in LoginViewController(authService: r.resolve(AuthServiceProtocol.self)!)}
// Usolet controller = container.resolve(LoginViewController.self)!controller.authenticate(user: "admin", pass: "1234") // Inicio de sesión exitosoGestión del ciclo de vida (Scopes)
Swinject admite cuatro scopes integrados:
- .transient — se crea una nueva instancia cada vez (por defecto)
- .graph — una instancia por grafo de resolución (usado por defecto en la mayoría de los casos)
- .container — singleton dentro del contenedor
- .weak — referencia débil, el obje