Problema
Al trabajar con procesos externos en aplicaciones Swift, la clase tradicional Process presenta varias limitaciones importantes:
- Gestión manual de recursos: Requiere configuración explícita de URLs ejecutables, argumentos y manejo de salidas
- Falta de soporte async/await: Utiliza métodos síncronos que bloquean el hilo de ejecución
- Manejo complejo de errores: Dificulta la captura y procesamiento de errores del proceso hijo
- Configuración verbosa: Cada ejecución requiere múltiples líneas de configuración repetitiva
En el código anterior, necesitaba ejecutar Ghostscript para convertir archivos PDF a imágenes PNG, pero la implementación con Process resulta extensa y poco elegante.
func _PDFToImages(
_ fileName: PathEnum
) async throws {
let process = Process()
process.executableURL = URL(
fileURLWithPath: "/opt/homebrew/bin/gs"
)
process.arguments = [
"-dNOPAUSE", "-dBATCH",
"-dQUIET", "-sDEVICE=png16m",
"-r300",
"-sOutputFile=\(fileName.rawValue)-%d.png",
fileName.rawValue.appending(".pdf"),
]
try process.run()
process.waitUntilExit()
}
Solución
La nueva librería Subprocess de Apple proporciona una API moderna y robusta para la ejecución de procesos en Swift. Esta librería multiplataforma ofrece soporte nativo para async/await y gestión automática de recursos.
Características principales:
✅ API nativa async/await para operaciones no bloqueantes.
✅ Gestión automática de recursos y limpieza de procesos.
✅ Control granular de salidas (stdout, stderr).
✅ Verificación de estado de terminación integrada.
✅ Sintaxis concisa y expresiva.
La implementación modernizada utiliza la función run de Subprocess:
func _PDFToImages(
_ fileName: PathEnum
) async throws {
let result = try await run(
.path(.init("/opt/homebrew/bin/gs")),
arguments: [
"-dNOPAUSE", "-dBATCH",
"-dQUIET", "-sDEVICE=png16m",
"-r300",
"-sOutputFile=\(fileName.rawValue)-%d.png",
fileName.rawValue.appending(".pdf"),
],
output: .discarded,
error: .string(limit: .max)
)
try guardAndLogError(
result.terminationStatus == .exited(0),
message: result.standardError,
status: .internalServerError
)
}
Parámetros clave:
- .path: Especifica el ejecutable de forma directa
- output: .discarded: Descarta la salida estándar ya que no necesitamos procesarla
- error: .string(limit: .max): Captura errores como string para logging
- result.terminationStatus: Verifica que el proceso terminó exitosamente
Resultado
La migración a Subprocess transforma el código de gestión de procesos en una solución más limpia, segura y eficiente:
Beneficios obtenidos:
🚀 Rendimiento mejorado con operaciones asíncronas reales.
🔒 Mejor manejo de errores con captura integrada de stderr.
📝 Código más legible con menos configuración manual.
⚡ Integración perfecta con el ecosistema moderno de Swift concurrency.
La nueva implementación no solo reduce la complejidad sino que también mejora la robustez del sistema al proporcionar mejor visibilidad de errores y un manejo más elegante de la ejecución asíncrona en aplicaciones Vapor.
Nota sobre DYLD_LIBRARY_PATH
En versiones anteriores de Swift, existía un problema conocido con la variable de entorno DYLD_LIBRARY_PATH al ejecutar procesos externos. Debido a las restricciones de System Integrity Protection (SIP) de macOS, esta variable era eliminada automáticamente al lanzar subprocesos, lo que causaba errores de “Library not loaded” en ciertos casos.
La solución temporal requería configurar manualmente las rutas de las librerías usando install_name_tool con @rpath, o bien establecer la variable DYLD_LIBRARY_PATH en su lugar.
¡Buenas noticias! 🎉 Este problema ha sido resuelto en las versiones más recientes de Swift. La librería Subprocess maneja correctamente las variables de entorno del sistema, incluida DYLD_LIBRARY_PATH, sin necesidad de configuraciones adicionales.
Keep coding, keep running 🏃♂️