Estructurando Proyectos Multi-Módulo con Swift
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/echo-app-swift
En esta lección vamos a dar un paso más allá del típico archivo main.swift
: aprenderás a estructurar un proyecto Swift en múltiples módulos usando Swift Package Manager (SwiftPM), la herramienta oficial para construir, distribuir y organizar código en Swift.
Dividiremos el proyecto en tres partes:
- Un ejecutable (
App
) que actúa como punto de entrada, - Una biblioteca (
Lib
) que contiene la lógica reutilizable, - Y un conjunto de pruebas (
LibTests
) que valida esa lógica.
Este enfoque modular no solo mejora la organización del código, sino que sienta las bases para proyectos más escalables, testeables y fáciles de mantener — como los que se esperan en entornos profesionales.
Diseñar un proyecto en módulos bien definidos te permite trabajar en capas, reutilizar lógica en múltiples contextos, y acelerar los ciclos de desarrollo y prueba.
Al terminar esta lección, estarás en condiciones de construir tus propias bibliotecas Swift, integrarlas en aplicaciones y ejecutar proyectos bien estructurados desde la terminal.
Let's-a go!
🏗️ Estructura esperada del proyecto
SwiftPM impone una convención estricta de carpetas para que los módulos (targets) funcionen correctamente.
A continuación se muestra la estructura completa que debe seguir el proyecto EchoAppSwift
para que compile correctamente y mantenga una separación clara entre ejecutable, biblioteca y pruebas:
Este diagrama muestra cómo organizar un proyecto modular en Swift utilizando Swift Package Manager (SwiftPM), respetando sus convenciones:
Package.swift
: Archivo central de configuración donde se declaran los módulos (App
,Lib
,LibTests
) y sus relaciones.Sources/App/
: Contiene el ejecutable principal. SwiftPM buscará aquí un archivomain.swift
como punto de entrada del programa.Sources/Lib/
: Módulo de biblioteca reutilizable. Puede ser importado porApp
o cualquier otro módulo.Tests/LibTests/
: Módulo de pruebas unitarias paraLib
. SwiftPM asocia automáticamente esta carpeta con el targetLibTests
.
📌 Los nombres de las carpetas dentro de Sources/
y Tests/
deben coincidir exactamente con los nombres de los targets definidos en Package.swift
.
📦 Paso 1: Crear la estructura de carpetas
Desde la raíz del proyecto:
- Windows
- Windows (corto)
- Linux/macOS
'App', 'Lib' | ForEach-Object {
$target = "Sources/$_"
New-Item $target -ItemType Directory -Force | Out-Null
switch ($_) {
'App' {
New-Item "$target/main.swift" `
-ItemType File -Force | Out-Null
}
'Lib' {
$echo = 'Echo'
New-Item "$target/$echo.swift", `
"Tests/${echo}Tests.swift" `
-ItemType File -Force | Out-Null
}
}
}
Este script inicializa la estructura de un proyecto Swift con dos módulos:
Sources/App/
: contiene el archivomain.swift
como punto de entrada para la aplicación.Sources/Lib/
: define la biblioteca reutilizable enEcho.swift
.Tests/
: contiene el archivoEchoTests.swift
para pruebas automatizadas de la biblioteca.| Out-Null
se usa para suprimir la salida de la consola al crear directorios y archivos.
El uso de switch
permite generar diferentes archivos según el módulo, y las rutas se construyen dinámicamente para mantener el código claro y compacto.
'App', 'Lib' | % {
$src = "Sources/$_"
md $src -f > $null
switch ($_) {
'App' { ni "$src/main.swift" -f > $null }
'Lib' { ni "$src/Echo.swift", "Tests/EchoTests.swift" -f > $null }
}
}
Este bloque configura la estructura inicial de un proyecto Swift dividido en dos módulos:
- Crea los directorios
Sources/App
ySources/Lib
. - En
App
, genera el archivomain.swift
, punto de entrada de la aplicación. - En
Lib
, creaEcho.swift
(lógica reutilizable) yEchoTests.swift
dentro deTests
para futuras pruebas.
El uso de > $null
en cada comando suprime la salida innecesaria en la consola, manteniendo el script limpio y silencioso.
mkdir -p Tests # Asegura que la carpeta de pruebas exista
for module in App Lib; do
src="Sources"
mkdir -p "$src/$module" # Crea la carpeta del módulo
case "$module" in
App)
touch "$src/$module/main.swift" # Archivo principal de la app
;;
Lib)
touch "$src/$module/Echo.swift" # Lógica compartida (biblioteca)
touch "Tests/EchoTests.swift" # Archivo de pruebas para la biblioteca
;;
esac
done
Este script configura un proyecto Swift modular:
- Crea los directorios
Sources/App
ySources/Lib
, donde vivirán los archivos fuente. - Si el módulo es
App
, generamain.swift
, el punto de entrada de la aplicación. - Si el módulo es
Lib
, creaEcho.swift
para la lógica reutilizable yEchoTests.swift
dentro deTests
para escribir pruebas más adelante.
El uso de case
mejora la claridad respecto a múltiples condiciones, facilitando la escalabilidad del script si se agregan más módulos en el futuro.
🧩 Paso 2: Declarar módulos y productos
Vamos a definir la configuración del proyecto en Package.swift
. Aquí declaramos:
- Un ejecutable llamado
App
, que actuará como punto de entrada. - Una biblioteca llamada
Lib
, que contendrá la lógica reutilizable. - Un conjunto de pruebas unitarias llamado
LibTests
.
// swift-tools-version: 6.1
import PackageDescription
let package = Package(
name: "echo-app-swift",
products: [
.executable(name: "App", targets: ["App"]), // Ejecutable principal
.library(name: "Lib", targets: ["Lib"]) // Biblioteca compartida
],
targets: [
.target(name: "Lib"), // Lógica reutilizable
.executableTarget(
name: "App",
dependencies: ["Lib"] // App depende de Lib
),
.testTarget(
name: "LibTests",
dependencies: ["Lib"] // Pruebas para la biblioteca
)
]
)
Este archivo configura el proyecto como un paquete de Swift multiplataforma con una estructura modular.
App
será el ejecutable que se puede lanzar desde consola.Lib
es la biblioteca donde colocamos la lógica de negocio (como la funciónecho
).LibTests
es un módulo de pruebas para verificar el comportamiento deLib
.
Al declarar estas relaciones, el compilador entiende cómo construir cada componente y cómo se relacionan entre sí.
🧪 Paso 3: Implementar la lógica reutilizable
Vamos a definir una función echo
dentro de la biblioteca Lib
. Esta función será pública para que el módulo App
pueda usarla.
public func echo(_ message: String) -> String {
return message
}
En este paso declaramos una función pública echo
, que recibe un mensaje y lo devuelve sin cambios.
- Usamos
public
para que otros módulos —comoApp
— puedan acceder a esta función. - La función encapsula una responsabilidad simple, pero servirá como ejemplo para entender cómo se comparte lógica entre módulos.
Este archivo representa la unidad mínima de lógica de negocio dentro de la biblioteca Lib
.
🚀 Paso 4: Crear el punto de entrada de la aplicación
Ahora implementamos la función principal de la aplicación en el módulo App
, que utilizará la función echo
de la biblioteca Lib
.
import Lib
func main() {
// Ignora el primer argumento (nombre del ejecutable)
let args = CommandLine.arguments.dropFirst()
for arg in args {
print(echo(arg))
}
}
main()
Este archivo define el punto de entrada del programa:
import Lib
habilita el uso de funciones públicas del móduloLib
, comoecho
.CommandLine.arguments
captura los argumentos pasados por terminal.dropFirst()
elimina el primer argumento, que es el nombre del binario ejecutado.print(echo(arg))
imprime cada argumento utilizando la función definida en la biblioteca.
Esta estructura demuestra cómo separar la lógica de aplicación (App) de la lógica de negocio (Lib), siguiendo buenas prácticas de diseño modular.
▶️ Paso 5: Ejecutar la aplicación
Una vez que has definido los módulos y escrito el código, puedes ejecutar la aplicación directamente desde la terminal con:
swift run App Kanna Kenji Friend
Kanna
Kenji
Friend
Este comando compila el proyecto (si es necesario) y ejecuta el módulo App
con los argumentos Kanna
, Kenji
y Friend
.
Cada argumento se imprime en una nueva línea. Esto demuestra que:
App
puede consumir funciones públicas deLib
.- El programa responde correctamente a entradas dinámicas.
- La separación entre biblioteca y aplicación funciona de forma efectiva.
✅ ¡Tu primer programa modular en Swift está en marcha!
🎯 Conclusiones
En esta lección aprendiste a estructurar un proyecto en Swift utilizando una arquitectura modular basada en Swift Package Manager (SwiftPM).
Partiste desde cero y construiste una estructura que separa claramente la lógica de aplicación (módulo App
) de la lógica reutilizable (módulo Lib
). Además, sentaste las bases para un desarrollo profesional al incluir un módulo de pruebas (LibTests
), aun cuando todavía no hayas escrito los tests.
Esta separación no solo mejora la organización del proyecto, sino que te prepara para escalarlo, reutilizar código entre proyectos, y aplicar buenas prácticas como pruebas unitarias e integración continua.
🔑 Puntos clave
- SwiftPM permite definir ejecutables, bibliotecas y pruebas dentro de un mismo proyecto, usando una convención clara de carpetas.
- El archivo
Package.swift
es el punto central para declarar módulos y sus relaciones. App
actúa como punto de entrada y puede depender de bibliotecas comoLib
.Lib
contiene la lógica reutilizable, y su visibilidad se controla con el modificadorpublic
.LibTests
permite probar la biblioteca de forma aislada.- La separación modular mejora la escalabilidad, el mantenimiento y la testeabilidad del proyecto.
🧰 ¿Qué nos llevamos?
Con esta lección pasaste de tener un único archivo Swift a trabajar con una estructura profesional y escalable, donde cada módulo cumple un rol bien definido.
Aprendiste a:
- Respetar las convenciones de SwiftPM.
- Diseñar una arquitectura basada en separación de responsabilidades.
- Compartir funcionalidad entre módulos mediante
import
. - Ejecutar un programa con argumentos desde la línea de comandos.
Este enfoque modular será la base para futuras lecciones en las que exploraremos pruebas, documentación, empaquetado y automatización. Ya estás pensando como quien construye software, no solo como quien lo escribe.
📖 Referencias
🔥 Recomendadas
- 🌐 "Package Manager" de la documentación oficial de Swift: Explica los fundamentos de Swift Package Manager (SwiftPM), incluyendo cómo definir módulos (
targets
), productos (products
) y dependencias. La lección usa precisamente estos conceptos para estructurar el proyecto en módulos (App
,Lib
,LibTests
) y compartir lógica medianteimport
. Esta lectura es clave para entender cómo SwiftPM organiza, compila y vincula proyectos modulares de manera automática y escalable.
🔹 Adicionales
- 📰 "Modular Project Structure with Swift Package Manager (SPM)" de Santosh Botre: Explica cómo estructurar aplicaciones iOS en múltiples módulos usando Swift Package Manager para lograr escalabilidad, reutilización y aislamiento. Presenta un enfoque práctico para dividir el código en paquetes locales (como
Network
,Logger
,Login
, etc.) y vincularlos al proyecto. Es relevante para esta lección porque ilustra los beneficios y el proceso de migrar de un diseño monolítico a una arquitectura modular realista con SwiftPM.