Introducción: por qué el perfilado de código Swift es la habilidad nº1 para el desarrollador iOS/macOS
Las aplicaciones modernas de iOS y macOS son cada vez más complejas, y los usuarios, más exigentes. Nadie esperará 5 segundos a que se cargue una pantalla, ni tolerará animaciones entrecortadas. La optimización del rendimiento no es un lujo, sino una necesidad. Pero, ¿cómo saber qué parte del código está ralentizando toda la aplicación? La respuesta es el perfilado de código Swift.
El perfilado es el proceso de recopilar datos sobre el funcionamiento del programa: tiempo de ejecución de funciones, uso de memoria, frecuencia de llamadas. Sin él, cualquier intento de optimización se convierte en un juego de adivinanzas. En este artículo, analizaremos herramientas y técnicas prácticas que te ayudarán a encontrar cuellos de botella en el código Swift para iOS y macOS. Aprenderás a usar Instruments, os_signpost y las herramientas integradas de Xcode, y verás ejemplos de código reales.
1. Herramientas de perfilado de Swift en Xcode
Xcode proporciona un potente conjunto de herramientas de perfilado, agrupadas en la aplicación Instruments. Estas son las principales que serán útiles para todo desarrollador:
- Time Profiler — muestra cuánto tiempo de CPU se gasta en cada función. Ideal para encontrar «puntos calientes» (hot spots).
- Allocations — rastrea la asignación y liberación de memoria. Ayuda a encontrar fugas y consumo excesivo.
- Leaks — busca automáticamente fugas de memoria (retain cycles).
- Core Animation — analiza el rendimiento de gráficos y animaciones.
- os_signpost — API para añadir manualmente marcadores en el código, que luego son visibles en Instruments.
Iniciar el perfilado es sencillo: selecciona un esquema (Scheme) en Xcode, pulsa Cmd + I (Product → Profile) y elige la plantilla deseada en Instruments.
1.1. Primer inicio: Time Profiler
Supongamos que tienes una aplicación que procesa lentamente una lista de 10 000 elementos. Inicia Time Profiler y ejecuta el escenario. Tras unos segundos, verás la pila de llamadas ordenada por tiempo. Así es como se ve en el código:
func processLargeArray() { let data = (0..<10000).map { $0 } let result = data.filter { $0 % 2 == 0 }.map { $0 * 2 } print(result.count)}Time Profiler mostrará que filter y map consumen la mayor parte del tiempo. Pero esto es esperable. ¿Y si el problema está en otro lugar? Por ejemplo, en la llamada frecuente a una función costosa dentro de un bucle.
2. Ejemplo práctico: optimización del trabajo con Core Data
Uno de los problemas comunes en iOS son las consultas lentas a Core Data. Veamos un ejemplo: cargar 5000 objetos con filtrado por nombre.
func fetchUsers(with name: String) -> [User] { let request = User.fetchRequest() request.predicate = NSPredicate(format: "name == %@", name) request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] return (try? context.fetch(request)) ?? []}Al ejecutar Time Profiler, puedes ver que el fetch tarda 200 ms, que es mucho. Pero, ¿por qué? Quizás no se ha creado un índice para el campo name. En Xcode, añade el índice en el modelo de datos:
- Abre el archivo .xcdatamodeld.
- Selecciona la entidad
User. - En el inspector de datos (Data Model Inspector), añade
nameen la sección Indexes.
Después de esto, un nuevo perfil mostrará una reducción del tiempo a 20–30 ms. ¡Una diferencia de 10 veces!
2.1. Uso de os_signpost para marcado manual
A veces, las herramientas estándar no proporcionan el nivel de detalle necesario. Entonces, os_signpost viene al rescate. Permite marcar manualmente el inicio y el final de la sección de código que te interesa, y luego ver esas marcas en Instruments.