Inyección de Dependencias en Swift: principios y ejemplos para iOS/macOS

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción a Dependency Injection en Swift



Dependency Injection (DI) es uno de los principios clave de diseño que ayuda a crear aplicaciones flexibles, testeables y mantenibles en Swift. En el contexto del desarrollo iOS y macOS, DI permite separar la creación de objetos de su uso, lo que simplifica enormemente las pruebas y la modificación del código.



La idea principal es que una clase no debe crear sus propias dependencias. En su lugar, las dependencias se "inyectan" desde el exterior. Esto es una implementación del Principio de Inversión de Dependencias (Dependency Inversion Principle) de SOLID.



¿Por qué es importante Dependency Injection para el desarrollo iOS/macOS?



En las aplicaciones nativas en Swift, a menudo nos encontramos con el problema del acoplamiento fuerte (tight coupling). Consideremos un ejemplo típico:



class UserService {    let networkManager = NetworkManager()    let database = Database()        func fetchUser(id: Int) -> User {        // lógica    }}


Aquí, UserService está fuertemente acoplado a las implementaciones concretas de NetworkManager y Database. Esto crea varios problemas:



  • Dificultad para realizar pruebas — no es posible reemplazar los servicios reales por mocks o stubs
  • Baja reutilización — la clase no se puede usar con otras implementaciones
  • Violación del SRP — la clase no solo se ocupa de la lógica de negocio, sino también de la creación de dependencias


Dependency Injection resuelve estos problemas, permitiendo inyectar dependencias a través del constructor, propiedades o métodos.



Formas de implementar Dependency Injection en Swift



1. Inyección por constructor (Constructor Injection)



La forma más común y recomendada. Las dependencias se pasan a través del inicializador:



protocol NetworkService {    func request(endpoint: String) -> Data}

protocol DatabaseService { func save(user: User)}

class UserService { private let networkService: NetworkService private let databaseService: DatabaseService init(networkService: NetworkService, databaseService: DatabaseService) { self.networkService = networkService self.databaseService = databaseService } func fetchUser(id: Int) -> User? { let data = networkService.request(endpoint: "users/\\(id)") // parseo de datos return User(id: id, name: "John") }}


Ventajas: las dependencias son explícitamente visibles, obligatorias para el funcionamiento de la clase, fáciles de testear.



2. Inyección por propiedades (Property Injection)



Las dependencias se establecen después de crear el objeto:



class AuthManager {    var networkService: NetworkService?        func login(username: String, password: String) -> Bool {        guard let service = networkService else { return false }        // lógica de autenticación        return true    }}

// Usolet authManager = AuthManager()authManager.networkService = RealNetworkService()


Desventaja: las dependencias pueden no estar establecidas, lo que lleva a tipos opcionales y posibles crashes.



3. Inyección por método (Method Injection)



La dependencia se pasa directamente al método:



class DataProcessor {    func process(data: Data, using service: NetworkService) -> ProcessedData {        let result = service.request(endpoint: "process")        // procesamiento        return ProcessedData()    }}


Adecuado para casos donde la dependencia solo es necesaria para un método.



Ejemplo práctico: aplicación para gestión de tareas



Creemos una aplicación simple para iOS que use DI:



// Protocolos de dependenciasprotocol TaskRepository {    func fetchTasks() ->                

Blogs

Book Recommendations