En la serie anterior de modularización resumimos los principios de los límites, la dirección de las dependencias y la estructura por capas. Ahora toca aplicarlos a un proyecto iOS.
En iOS, básicamente hay dos herramientas para dividir módulos: los destinos de framework de Xcode y Swift Package (SPM, Swift Package Manager).
En resumen, en 2026 SPM es la opción predeterminada para una modularización nueva. Los módulos se crean solo con carpetas y Package.swift, sin conflictos en el archivo de proyecto (pbxproj).
Este artículo resume cómo crear módulos locales con SPM, sus diferencias frente a los destinos de framework y cómo elegir entre enlazado estático y dinámico.
¿Por qué SPM en lugar de destinos de framework?
También puedes crear un módulo en Xcode mediante File → New → Target → Framework. Durante mucho tiempo fue el enfoque estándar.
El problema son las modificaciones que deja. Cada destino añadido cambia project.pbxproj en decenas de líneas, y si dos miembros del equipo modifican destinos a la vez aparecen conflictos de merge.
Los paquetes SPM locales son distintos.
| Comparación | Destino de framework | Paquete SPM local |
|---|---|---|
| Ubicación de la definición del módulo | project.pbxproj | Package.swift (código declarativo) |
| Conflictos de merge | Frecuentes | Poco frecuentes (el archivo contiene código legible) |
| Coste de añadir un módulo | Operación en la GUI de Xcode | Carpeta + unas líneas |
| Configuración de compilación detallada | Flexible | Limitada |
Cuantos más módulos haya, más determina la arquitectura el coste de añadir uno. Si añadirlos da miedo, nadie dividirá el sistema.
Salvo que necesites controlar con gran detalle la configuración de compilación, los paquetes SPM locales ofrecen un menor coste de mantenimiento.
¿Cómo se crea un paquete SPM local?
Crea una carpeta junto al proyecto de la app y coloca allí Package.swift. Traslademos tal cual la estructura por capas del artículo anterior.
// Modules/Package.swift
let package = Package(
name: "Modules",
products: [
.library(name: "FeatureSearch", targets: ["FeatureSearch"]),
.library(name: "CoreNetwork", targets: ["CoreNetwork"]),
],
targets: [
.target(name: "FeatureSearch", dependencies: ["CoreNetwork"]),
.target(name: "CoreNetwork"),
.testTarget(name: "FeatureSearchTests", dependencies: ["FeatureSearch"]),
]
)
Arrastra este paquete al proyecto de Xcode, conecta la biblioteca al destino de la app y funcionará import FeatureSearch.
Lo importante es que las dependencias se declaran como código en el arreglo dependencies. Si FeatureSearch no debe usar CoreNetwork, elimínalo del arreglo; un import oculto provocará un error de compilación.
Así se crean los “límites impuestos por el compilador” mencionados en el artículo anterior.
En lugar de colocar varios destinos en un paquete, también puedes usar un paquete por módulo. Con pocos módulos, un único paquete con varios destinos es más fácil de gestionar; cuando son decenas, muchos equipos dividen los paquetes por dominio.
¿static o dynamic? ¿Cuál elegir?
Al declarar .library puedes elegir el modo de enlazado. El valor predeterminado es automatic, que Xcode decide automáticamente y normalmente procesa como static.
- static: el código del módulo se copia y combina con el binario de la app. No hay carga adicional al iniciar la app.
- dynamic: el módulo existe como un archivo de framework independiente y se carga en tiempo de ejecución.
Estos son los criterios de elección.
| Situación | Elección |
|---|---|
| Módulo de funcionalidad o núcleo habitual | static (mantener el valor predeterminado) |
| La app, el widget y la extensión comparten el mismo módulo | dynamic (eliminar duplicación del binario) |
| El tiempo de inicio es crítico, pero hay decenas de módulos dynamic | Considerar consolidarlos como static |
Más frameworks dynamic aumentan el coste de carga al iniciar la app. Apple lo ha recomendado de forma constante en sesiones de WWDC; salvo que haya un motivo especial, mantener automatic (en la práctica, static) es una opción razonable.
¿Cuándo deja de bastar SPM por sí solo?
Puedes empezar con la modularización mediante SPM y usarla bien, pero los equipos grandes terminan encontrando limitaciones.
- La configuración propia del destino de la app (firma, esquemas y configuración de compilación) sigue en pbxproj, por lo que puede haber conflictos.
- Con decenas de módulos, gestionar Package.swift y unificar las plantillas de módulos se vuelve tedioso.
- Quieres compartir la caché de compilación a nivel de organización.
La herramienta que aparece en este punto es Tuist. Será el tema del último artículo de la serie.
Así se plantea en una entrevista
P. ¿Por qué usar paquetes SPM locales para modularizar iOS?
Las definiciones de módulos se gestionan como código declarativo en Package.swift, en lugar de pbxproj, lo que reduce los conflictos de merge y el coste de añadir módulos. Además, las dependencias entre destinos aparecen explícitas en dependencies, por lo que los import no permitidos se bloquean con errores de compilación y el compilador impone los límites de los módulos.
P. Explica la diferencia entre bibliotecas static y frameworks dynamic, y los criterios para elegirlos.
static se copia al binario de la app durante el enlazado, sin coste de carga en ejecución; dynamic existe como archivo independiente y se carga en tiempo de ejecución. Usa static por defecto, pero elige dynamic cuando la app, las extensiones y los widgets compartan código y haya que reducir la duplicación del binario.
El próximo artículo trata el verdadero motivo por el que muchos equipos comienzan a modularizar: la velocidad de compilación de Xcode.
Veremos por qué las compilaciones se ralentizan, dónde y cuánto mejora la modularización, y cómo medir numéricamente ese efecto.

![Imagen de portada de [Modularización #4] Modulariza iOS con SPM](/assets/images/posts/2dfa8b93-209b-47ab-8ad8-2745ec0ab314/1.jpg)