Local Packages con Swift Package Manager

Recientemente he estado usando Swift Package Manager, en este post me enfocaré en una característica de SPM que me ha sido de mucha utilidad para la creación de frameworks internos y modularización en general: local swift packages.

Para una guía completa de este potente administrador de dependencias, recomiendo empezar por aquí:

Porqué usar swift packages?

  • Permiten agrupar código relacionado y organizarlo en una unidad individual que se pueda compilar por sí sola.
  • Permite la reutilización de paquetes en múltiples proyectos, disminuye el tiempo de desarrollo, puedes trabajar de paralelamente con tus compañeros.
  • Si estoy trabajando con la aplicación principal o con otro package, los packages no tienen que recompilarse.
  • Soporte para compilación en paralelo.
  • Es la aplicación de Responsabilidad única (SOLID) aplicada a un nivel más alto.

Cómo empezar.

  1. Dentro de tu proyecto crear un folder donde se agruparán los packages a crear, podrías llamarla DevPackages por ejemplo.
  2. Ejecutar “swift package init” dentro de la carpeta
  3. Listo, ya tengo mi primer package creado.

Qué es el archivo package.swift ?

Este archivo describe la configuración de tu paquete, puedes incluir aquí dependencias, plataformas a soportar, versión mínima, etc.

Para ver todos los atributos que soporta: https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageDescription.md

A diferencia de Cocoa Pods el archivo de configuración utiliza sintaxis en Swift.

A modo de ejemplo crearé un package que agrupa la lógica para obtener Emojis.

La idea es encapsular una tarea dentro de un package, internamente el package puede crecer y ser más complejo (cachear emojis, obtenerlos de un API remota, etc) e incluso usar otros packages.

Empezando a trabajar en mi package

Básicamente puedes trabajarlo de 2 formas

  1. Usando el archivo swift.package

Por defecto Xcode tiene soporte para manejar estos archivos, solo lo abres y empiezas a trabajar en este.

El hecho de que puedas trabajarlo independiente de tu app principal es muy útil, los tiempos de compilación y la ejecución de tests suele ser muy rápida.

  1. Integrándolo a mi proyecto principal.

Este es un paso no tan claro, pero puedes arrastrar la carpeta que contiene a tu package hacia tu proyecto.

Xcode detecta los Swift packages automáticamente.

Para empezar a tu package necesitar hace un par de pasos adicionales:

Desde la configuración de tu proyecto -> General -> Agregar Framework

Seleccionar el Package previamente agregado a tu proyecto.

Listo Xcode ya puedes empezar a usarlo e incluso editarlo desde tu proyecto principal.

Si desde tu proyecto principal en algún momento vas modificar código relacionado al package, recomiendo seleccionar el Schema autogenerado, para evitar compilar todo el proyecto, esto disminuirá los tiempos de compilación en tiempos de desarrollo.

Agregando dependencias externas a un package

Es un caso común que dentro de un package necesite una dependencia de un tercero.

Para hacer esto editamos nuestro archivo package.swift

Tan simple como eso, después de esto ya podemos utilizar AlamoFire dentro de nuestro package.

Un package necesitar usar otro Local Package

Otro caso de uso común, un Package A depende de un Package B

Para este caso se tendrás que usar otro argumento de la función package, este hará referencia a la ruta relativa de nuestro proyecto

Access Control

Al usar local packages, te fuerza a pensar más en el control de acceso de tus clases, structs.

Por defecto solo lo declarado como public será accesible desde otro módulo, así que esto nos obliga a manejar qué modelos deberían ser públicos y cuales mantenerse ocultos.

Un caso común son los inicializadores de las estructuras que swift nos asigna automáticamente.

Por defecto este init es internal, así que si deseamos utilizar la estructura fuera de nuestro módulo, tendremos que declarar un inicializador explícito.

Accediendo a Assets desde un Package

SPM automáticamente te genera una extensión con la variable “module”, que hace más sencillo el acceso a recursos dentro de este.

Cómo inyectar dependencias a mi package?

Esto me obliga a pensar en el diseño de una forma donde pueda trabajar en la lógica de mi package independientemente de las dependencias.

Por ejemplo tenemos un package Logger que queremos usar dentro de nuestro package EmojiMapper.

Para lograr esto declaramos un protocolo y una clase concreta.

La idea es que EmojiMapper dependa del protocolo en vez de la clase en concreto.

Debido a que las interfaces de un módulo cambian con menos frecuencia que las implementaciones, esto me permite lograr cierta abstracción, favoreciendo el desacoplamiento y las ventajas que esto conlleva.

Packages como Wrappers de Dependencias de Terceros

Es común que algunos packages se terminar convirtiendo en una especie de wrapper de alguna dependencia de terceros.

Esto es una práctica común para evitar utilizar una dependencia a lo largo de toda la aplicación y tenerla centralizada en un solo punto.

Si bien es cierto no podría ser aplicable para dependencias de la magnitud como RxSwift o IGLisKit, si podríamos hacerlo para dependencias donde dependemos de unos cuantos métodos.

La idea es crear un protocolo propio y encapsular la dependencia dentro de una clase concreta de nuestro protocolo.

Ahora mis packages o mi app principal dependería de mi protocol, bueno en realidad si depende de la dependencia de tercero pero solo en tiempo de ejecución.

Ideas para tus propios Packages

Estas son algunas ideas para crear tus propios packages:

  • Analytics
  • Logging
  • Persistencia (Wrapper de Realm o CoreData
  • Image Manager
  • UI 
  • Networking
  • Feature Package (Wrapper alrededor de cierta funcionalidad de tu app, Login, Onboarding, Home, Account, Settings)

Conclusión

Te invito crear tu primer package.

Pensar en un package como cierta funcionalidad que naturalmente se puede agrupar en un solo lugar, abstrayendo la funcionalidad dentro de un módulo y exponiéndola a través de una interface simple, fácil de usar, mockear y testear.

Leer más: