Inyección de Dependencias en Swift: principios, ejemplos e implementación

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

¿Qué es Dependency Injection y por qué es necesario en Swift?

Dependency Injection (DI) es un patrón de diseño en el cual un objeto recibe sus dependencias desde el exterior, en lugar de crearlas por sí mismo. En el contexto de Swift para iOS y macOS, DI ayuda a que el código sea más flexible, testeable y mantenible. En lugar de acoplar clases rígidamente mediante init() o let, se pasan las dependencias a través del constructor, una propiedad o un método.

Principales ventajas de DI:

  • Testeabilidad: se pueden reemplazar fácilmente objetos reales por mocks o stubs en pruebas unitarias.
  • Bajo acoplamiento: las clases dependen de abstracciones (protocolos), no de implementaciones concretas.
  • Simplificación del refactor: los cambios en una parte del código no rompen otra.
  • Arquitectura limpia: el código sigue el principio de responsabilidad única y el principio de inversión de dependencias (DIP de SOLID).

En Swift, DI es especialmente importante al trabajar con UIKit, SwiftUI, CoreData, redes y otros servicios. Sin él, se corre el riesgo de crear "objetos divinos" y dificultades para reutilizar el código.



Formas principales de inyección de dependencias en Swift

1. Inyección por inicializador (Constructor Injection)

La forma más común y recomendada. Las dependencias se pasan al crear el objeto y se vuelven inmutables (let).

protocol NetworkServiceProtocol {    func fetchData() -> String}

class NetworkService: NetworkServiceProtocol { func fetchData() -> String { return "Datos del servidor" }}

class ViewModel { private let networkService: NetworkServiceProtocol

// Inyección por inicializador init(networkService: NetworkServiceProtocol) { self.networkService = networkService }

func loadData() -> String { return networkService.fetchData() }}

// Usolet service = NetworkService()let viewModel = ViewModel(networkService: service)print(viewModel.loadData()) // "Datos del servidor"

Ventajas: las dependencias son explícitas, el objeto siempre está en un estado válido. Desventaja: con muchas dependencias, el constructor se vuelve voluminoso.



2. Inyección por propiedad (Property Injection)

La dependencia se establece después de crear el objeto. Se usa a menudo para servicios opcionales o al trabajar con Storyboard/XIB.

class UserViewController: UIViewController {    var analyticsService: AnalyticsServiceProtocol?

override func viewDidLoad() { super.viewDidLoad() analyticsService?.trackEvent("Pantalla abierta") }}

// Inyecciónlet vc = UserViewController()vc.analyticsService = AnalyticsService()

Desventaja: la propiedad debe ser opcional o tener un valor por defecto, lo que puede provocar errores en tiempo de ejecución si se olvida establecer la dependencia.



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

La dependencia se pasa como parámetro del método. Es adecuada para situaciones donde la dependencia solo se necesita para una operación.

class DataManager {    func processData(using service: NetworkServiceProtocol) -> String {        let rawData = service.fetchData()        return "Procesado: \\(rawData)"    }}

let manager = DataManager()let result = manager.processData(using: NetworkService())print(result) // "Procesado: Datos del servidor"


Ejemplo de implementación de DI en una aplicación iOS

Consideremos un ejemplo real: una aplicación para mostrar una lista de usuarios. Crearemos protocolos y clases con inyección de dependencias.

// 1. Protocolosprotocol UserRepositoryProtocol {    func getUsers() -> [String]}

protocol UserViewModelProtocol { var users: [String] { get } func loadUsers()}

// 2. Implementación del repositorioclass UserRepository: UserRepositoryProtocol {

Blogs

Book Recommendations