Como implementar Inyección de Dependencias en Swift

Hasta ahora nos estábamos preocupando más bien poco sobre un tema que si se hace bien nos puede traer grandes beneficios. Este no es otro que aplicar el principio de Inversión de Dependencias.

Si nos vamos a la Wikipedia, en este artículo se explica en detalle y nos dice esto en concreto:

  1. Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones (p.ej., interfaces).
  2. Las abstracciones no deberían depender de los detalles. Los detalles (implementaciones concretas) deben depender de abstracciones.

Así explicado la mayoría nos quedamos con cara de no entender nada. Pero si lo vemos con un ejemplo se entiende mejor.

En nuestra aplicación de ejemplo, en concreto en nuestro HomeViewModel, instanciamos un clase Configuration y otra ApiRestClient. Cuando empezamos a programar, esto suele ser lo más normal, necesitamos una clase que nos ayuda con un determinado tema, creamos una nueva instancia dentro de la clase que necesitamos y seguimos desarrollando.

Esto nos va a traer algunos problemas que a la larga nos pesarán bastante si nuestro código crece y crece (que lo hará).

  1. Imaginaros que tenemos una clase que almacena un dato concreto. Al principio, para no complicar la app, lo que hacemos es almacenar en UserDefaults, en nuestra clase instanciamos UserDefaults directamente y almacenamos. Además en otras clases que necesitamos leer este dato, vamos instanciando y leyendo el dato. Pasado un tiempo, se decide almacenar en base de datos, porque ahora se escriben más datos y en UserDefaults tantos datos son difíciles de manejar. Ahora, todas esas clases donde se estaba leyendo directamente a UserDefaults están fuertemente acopladas a este Framework.
  2. Por otro lado, si intentáis testear una de estas clases, os daréis cuenta de que es difícil hacerlo. No podremos testear por separado la funcionalidad en si de cada clase, vamos a tener siempre a UserDefaults interfiriendo sobre unos tests que se supone, deberían ser unitarios.

Bien, para solucionar todo este embrollo, llega la Inyección de Dependencias que sería la forma de aplicar el principio de Inversión de Dependencias. Siguiendo con el ejemplo anterior, una forma de solucionarlo sería pasando por constructor a todas las clases donde se lee o escribe una actracción.

Podemos tener una interface o protocol que nos diga que tenemos que implementar un método para leer y otro para escribir, pero esto no nos marca o exige como debemos hacerlo. Por otro lado, nos creamos una clase que implementa esta interface y que lee/escribe de UserDefaults, y se la pasamos por constructor a las clases que necesitamos. Cada una de estas clases ahora ya no tiene, ni que instanciar a UserDefaults, ni saber como se lee de este, y cambiar a otro sistema de almacenamiento sería mucho más sencillo, ya que simplemente se puede cambiar en la clase que maneja ahora la lectura/escritura de UserDefaults.

Bueno, dejando de lado la teoría y volviendo a nuestro proyecto. Estoy viendo que a nuestro proyecto le está pasando esto mismo, estamos instanciando dentro de clases otras, y esto va a suponer un problema a futuro. Para resolver en iOS estoy utilizando Resolver, que es una librería que nos ayudará con esto, pero revisando la documentación, me he dado cuenta que el creador va a deprecar la librería, pero tiene una nueva versión Factory, que se basa en la anterior, y básicamente nos permitirá hacer lo mismo.

Vamos con esta última entonces. Lo primero que tendremos que hacer es añadir la librería. Para ello, en File -> Add Packages y en la ventana que aparece, copiamos la url del repositorio https://github.com/hmlongco/Factory

En mi caso, he tenido problemas al añadir la librería, he tenido que añadir y eliminar 2 o 3 veces hasta que XCode la añadió correctamente y reconoció. Si usáis CocoaPods en vuestro proyecto, las instrucciones para instalar las podéis encontrar en el repositorio del autor de la librería.

Una vez añadida la librería, vamos a identificar aquellas clases que se están llamando dentro de otras.

El primer caso es el que comentamos antes, la clase HomeViewModel. Dentro de esta se está instanciando a la clase Configuration y a ApiRestClient.

Crearemos ahora un archivo nuevo que llamaremos AppInjection, en donde definiremos como instanciar estas clases. En este archivo importamos Factory y creamos una extension de Container (es una clase de nuestra librería).

Empezamos definiendo como instanciar Configuration. Creamos una propiedad configurationService y con la ayuda de Factory le decimos como debe instanciar Configuration. En este caso no necesita nada en el constructor.

Llegamos al caso de ApiRestClient. En este caso, necesitamos pasar en el constructor una instancia de Configuration. En este caso llamamos a nuestra propiedad configurationService.

import Factory


extension Container {
    static let configurationService = Factory(scope: .singleton) {
        Configuration()
    }
    
    static let apiRestClientService = Factory(scope: .singleton) {
        ApiRestClient(configuration: configurationService())
    }
}

Fijaros además que le estamos indicando a Factory en el parámetro scope el valor singleton. Con esto lo que conseguiremos es usar siempre la misma instancia de Configuration o ApiRestClient. El comportamiento por defecto es crear siempre una nueva instancia. Si deseamos otro comportamiento diferente se podría, os recomiendo leer la documentación de la librería.

Una vez tenemos esto listo vamos a ir un paso más allá, y añadiremos a Container también los ViewModels. Dentro de la extension Container añadimos:

static let homeViewModel = Factory() {
        HomeViewModel()
    }
    
    static let movieViewModel = Factory() {
        MovieViewModel()
    }

En este caso, no indicamos el scope, ya que queremos una nueva instancia de nuestros viewModel.

Una vez definido como se instancian nuestras clases, vamos a ver como utilizarlo.

Vamos a ViewModel, y vemos que tenemos algo así:

class HomeViewModel: ObservableObject {
    
    private let apiRestClient: ApiRestClient
    private let configuration: Configuration
    
    init() {
        self.configuration = Configuration()
        self.apiRestClient = ApiRestClient(configuration: configuration)
    }

Como veis, estamos instanciando dentro de nuestra clase Configuration y ApiRestClient. Veamos como librarnos de esto.

@Injected(Container.apiRestClientService) private var apiRestClient: ApiRestClient
    
init() {
        
}

Factory nos proporciona un wrapper @Injected, que nos permitirá obtener una instancia de la clase que necesitamos, y de este modo podremos limpiar el constructor.

En el caso de la vista, en lugar de utilizar @Injected, lo haré de la siguiente forma, ya que he detectado que junto a @ObservedObject la librería no funciona bien.

@ObservedObject var viewModel: HomeViewModel = Container.homeViewModel()

Y de este modo Factory nos dará una instancia del ViewModel que necesitamos. Se podría instanciar directamente el ViewModel en este punto, pero bajemos un momento hasta la preview. Y revisemos como cambia.

#if DEBUG
struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        let _ = Container.homeViewModel.register { MockHomeViewModel() }
        HomeView()
    }
}
#endif

Antes de esto, estábamos instanciando la vista y el viewModel, y asignando el ViewModel a la vista. Ahora lo que se hace es decirle a nuestro gestor de inyección de dependencias que vamos a cambiar nuestro HomeViewModel por MockHomeViewModel cuando mostremos la preview.

Esta misma técnica la podremos aplicar a la hora de hacer tests (que aún no llegamos, pero lo haremos).

Como conclusión, hemos modificado la forma en la que se asignan Configuration, ApiRestClient o los ViewModel, pero no la funcionalidad, aún así, la potencia que nos dará esto a medida que crezca el código será brutal, además de facilitarnos cambiar en nuestro código una pieza por otra con mucha facilidad. Por último, controlar como se instancia un objeto, a partir de ahora, se hará en un único sitio.

Como guardar configuraciones con Swift y iOS

La siguiente parte que quería abordar era como hacer peticiones a una API con Alamofire, pero cuando empecé me di cuenta que no quería exponer mi Api Key en el repositorio donde estoy subiendo el código de la aplicación.

Así que lo que vamos a hacer antes de llamar a nuestra API, será ver como almacenar correctamente nuestra Api Key de forma segura.

Lo primero que haremos será crear un nuevo archivo de tipo «Configuration Settings File» donde podremos incluir todos los valores sensibles.

Creo una entrada para mi api key, así:

Lo siguiente que haremos será ir a nuestro archivo Info.plist, que para los proyectos nuevos hechos con SwiftUI no existirá. En este caso, tenemos que ir en nuestro proyecto a la pestaña Info, y dentro de «Custom iOS Target Properties» añadir una clave nueva (pulsamos sobre la última y después Enter). Notaremos que después de hacer esto tenemos un nuevo archivo Info.plist.

Tenemos que añadir un nuevo par clave-valor. La key será «API_KEY» y el valor «$(API_KEY). Esta última es la que hace referencia a nuestro archivo de configuración del paso anterior.

Ahora, en nuestro proyecto, le decimos que archivo de configuración debe tomar. Podemos tener uno diferente para cada tipo de compilación si lo queremos. En este caso usaremos el mismo.

¿Cuándo podemos tener varios? Imaginaros que una de las claves fuese una url a un servidor. Para debug podría apuntar a un servidor de desarrollo y para release al de producción.

En nuestro archivo .gitignore debemos añadir una entrada para que no añada los cambios de nuestro archivo de configuración. Este archivo lo abro desde terminal con vim, pero aquí el editor es lo de menos.

Una vez ya tenemos todo configurado, vamos a utilizarlo en nuestra app.

Primero de todo creamos un archivo Configuration.swift. Aquí expondremos una propiedad con el valor de nuestra api key. Esto lo estaremos leyendo de nuestro archivo Info.plist.

class Configuration {
    
    let apiKey: String? = Bundle.main.infoDictionary?["API_KEY"] as? String
    
}

Ahora solo quedará instanciar nuestra clase Configuration donde se necesite este api key.

let configuration = Configuration()
configuration.apiKey

Como veis, es muy sencillo manejar nuestras api keys o cualquier otro parámetro de configuración.

Por supuesto, y por seguridad, no incluyáis claves en vuestra aplicación, en todo caso, si las necesitáis, tenedlas a buen recaudo en un servidor seguro, y solo obtenedlas bajo una conexión segura.

Podéis ver el código completo en el repositorio donde estamos haciendo nuestra aplicación para leer películas https://github.com/3pies/moviesios o podéis ver el proceso completo en mi canal de Youtube.

Como crear nuestro primer MVVM en iOS

Empezar a crear una aplicación desde cero siempre es excitante y a la vez abrumador. Demasiadas cosas que hacer en muy poco tiempo. Así que es importante pararse un momento y reflexionar como queremos hacerlo.

Un buen punto de inicio es pensar en una funcionalidad que nos permita definir a pequeña escala como queremos que sea la arquitectura de nuestra app, y a partir de ahí empezar a contruir.

En nuestro caso, una app que lea un listado y después vea un detalle nos ayudará a comprender y modelar nuestra app a pequeña escala.

Voy a utilizar MVVM como arquitectura, ya que nos ayudará a separar la parte vista del resto de la app. En esta arquitectura tenemos 3 partes, la parte View que es la que muestra la interface de usuario, la parte Model que es la que maneja los datos, y por último, la parte ViewModel, que por un lado se encarga de tratar los datos y por otro lado, pasa estos datos a la vista a través de databinding.

Vamos a empezar, voy a crear una vista muy sencilla, con su viewmodel y datos mock que nos permitirá tener un primer punto de arranque con estas 3 capas. Empecemos!!!

Model

Empezaré creando el modelo de datos, en este caso al tratarse de un listado de películas, haré un entidad llamada Movie, la cual será un struct plano y cuyo único cometido será almacenar los datos a mostrar.

Además, debe implementar el protocolo Identifiable, el cual nos ayudará después a la hora de iterar sobre un listado de Movie.

struct Movie: Identifiable {
    var id: String
    var imdbId: String?
    var title: String
    var overView: String
    var posterPath: String?  
}

Además, dado que no vamos a conectarnos ni con un API ni con base de datos local de momento, crearemos un array de tipo Movie con datos mock. Este nos quedará después para ayudarnos a crear las previews de nuestras vistas.

let moviesTest: [Movie] = [
    Movie(id: "1", imdbId: "imdb01", title: "Regreso al Futuro", overView: "Lorem ipsum", posterPath: nil),
    Movie(id: "2", imdbId: "imdb02", title: "Regreso al Futuro II", overView: "Lorem ipsum", posterPath: nil),
    Movie(id: "3", imdbId: "imdb03", title: "Regreso al Futuro III", overView: "Lorem ipsum", posterPath: nil),
    Movie(id: "4", imdbId: "imdb04", title: "Matrix", overView: "Lorem ipsum", posterPath: nil),
    Movie(id: "5", imdbId: "imdb05", title: "Canta", overView: "Lorem ipsum", posterPath: nil),
    Movie(id: "6", imdbId: "imdb06", title: "Scream", overView: "Lorem ipsum", posterPath: nil),
    Movie(id: "7", imdbId: "imdb07", title: "Spiderman No way home", overView: "Lorem ipsum", posterPath: nil),
]

ViewModel

El siguiente paso será crear nuestro ViewModel, es decir, la parte que se encargará de leer datos y enlazarlos a la vista.

Crearemos un archivo llamado HomeViewModel con lo siguiente:

import SwiftUI

class HomeViewModel: ObservableObject {
    
    @Published var movies: [Movie] = []
    
    func loadMovies() {
        self.movies = moviesTest
    }
    
}

Tenemos 3 puntos a destacar. Por un lado, nuestra clase ViewModel implementa ObservableObject, lo cual permitirá que se observe desde la vista.

Por otro lado, tenemos una propiedad marcada con @Published, el cual es un wrapper que nos permitirá enviar datos y que será observado desde la vista. La vista reaccionará a los cambios que se vayan produciendo desde aquí.

Por último, tenemos un método loadMovies que se encarga de lanzar la lectura de datos. En este caso concreto, simplemente estamos enlazando con nuestros datos mock, pero en futuras publicaciones se complicará esta parte, mientras que la vista permanecerá igual.

Ahora que tenemos 2 de 3 partes, vamos a por la última.

View

Esta es sin duda la más sencilla. De momento tendremos un listado que se nutrirá de los datos que vienen del viewModel.

struct HomeView: View {
    
    @ObservedObject var viewModel: HomeViewModel = HomeViewModel()
    
    var body: some View {
        VStack {
            ForEach(viewModel.movies) { movie in
                Text(movie.title)
            }
        }.onAppear {
            viewModel.loadMovies()
        }
    }
}

Para empezar definimos nuestro viewModel y lo marcamos con @ObservedObject, esto es un wrapper que le dirá a la vista que se va a observar este objeto.

Después, tenemos un VStack, en el cual añadimos un ForEach del viewModel.movies, junto con un Text, donde de momento solo mostraremos el título de nuestra película.

Hasta aquí, de por si, no pasará nada, pero gracias al modificador onAppear llamaremos al método que lee las películas en el momento en el que aparece la vista.

Y con esto ya hemos montado una arquitectura MVVM muy básica. Aunque muy básica ya hemos marcado los cimientos y separado los conceptos para construir nuestra aplicación.

Como siempre, os dejo enlace al repositorio de Github donde tengo el código completo. No olvidéis revisar el vídeo de Youtube asociado, suscribiros y dejar un gran like.

https://github.com/3pies/moviesios

Descubre como crear tu app iOS de cero a experto

Hace poco os presentaba una app Android que había creado para un proceso de selección. Podéis ver aquí el código de la aplicación https://github.com/3pies/movies

Además del video presentación que hice en mi canal de Youtube.

Y como no podía ser de otra forma, no podemos dejar a nuestros queridos usuarios de iOS sin su versión. Así que empezaré a crear en iOS una versión muy similar. En este caso utilizando SwiftUI.

Hoy nos tocará preparar nuestra app. Para ello lo primero que haremos será crear un nuevo proyecto, indicamos donde lo almacenaremos, en mi caso marcaré crear repositorio git (os recomiendo que vosotros también lo hagáis).

Como interface SwiftUI y lenguaje Swift. En mi caso no utilizaré Core Data, así que lo dejo sin marcar. Indicamos el Bundle Identifier y ya tendremos el proyecto listo.

Si recordáis, en el proyecto Android, tenía que:

  • Leer datos de un WS
  • Almacenar en base de datos local

Así que aprovecharemos y añadiremos nuestras primeras librerías. Para ello vamos a utilizar Swift Package Manager. Este es un gestor de dependencias similar a Cocoapods, y que ya viene integrado en el IDE XCode.

Para añadir nuevas librerías es muy sencillo, primero localizamos las que queremos añadir. En mi caso, voy a añadir primero Alamofire. Librería utilizada para hacer las peticiones a los WS. Vamos a su github y allí nos aparecen las instrucciones para añadir https://github.com/Alamofire/Alamofire#installation

En XCode, vamos a File -> Add Packages… En la ventana que aparece, arriba a la izquierda, en el buscador escribimos «https://github.com/Alamofire/Alamofire.git» y pulsamos «Add Package». Empezará a cargar hasta que finalmente acabe.

Una vez lista esta podemos añadir alguna más. Os dejo enlaces a los repositorios de:

  • Realm para almacenar de forma local https://github.com/realm/realm-swift.git
  • Resolver para añadir inyección de dependencias https://github.com/hmlongco/Resolver.git

Llegados a este punto, podremos ir a las propiedades de nuestro proyecto, en la sección «Package Dependencies» tendremos todas las dependencias que hemos añadido.

Si en cualquier momento estamos interesados en actualizar nuestras dependencias podemos ir a File -> Packages -> Update to Latest Package versions.

Una vez listas nuestras primeras librerías, ya estaremos preparados para empezar a desarrollar nuestra aplicación.

Por último, os dejo el enlace al repositorio donde voy a hacer la aplicación para iOS https://github.com/3pies/moviesios

iOS – Alamofire en 3 sencillos pasos

Cuando hacemos aplicaciones móviles, existen ciertas cosas que solemos necesitar en la mayoría de los casos. Una de ellas es nutrir nuestra aplicación con datos, en este caso veremos cómo leer datos de nuestra API HTTP.

Para poder leer datos de nuestras APIs lo más común es utilizar una librería que nos de una serie de utilidades ya empaquetadas y listas para utilizar. No queremos reinventar la rueda una y otra vez. Por eso, en este ejercicio, utilizatemos Alamofire, una de las librerías más populares.

Para empezar, necesitamos añadirlo a nuestro Podfile. Y en nuestra terminal ejecutamos un pod install para que se descargue.

También veremos como con el uso de objetos Codable, mapear las respuestas de nuestra API es muy sencillo.

Si no te quieres perder la explicación, haz click en el video. Y no olvides dejar tus comentarios o preguntas. Y si te gusta el vídeo, y quieres más, no olvides darle like y suscribirte.