Introducción: por qué perfilar código Swift es una necesidad
El desarrollo para iOS y macOS requiere alto rendimiento. Incluso un método lento puede causar lag en la UI, agotamiento de batería o congelamiento de la aplicación. Perfilar código Swift es el proceso de analizar el tiempo de ejecución, la memoria y el uso de CPU, que permite encontrar y eliminar cuellos de botella. Sin perfilamiento, la optimización se convierte en adivinanza: cambias el código al azar, sin saber qué es lo que realmente ralentiza.
En este artículo analizaremos las herramientas principales de perfilamiento de Swift: Xcode Instruments, os_signpost y la medición manual de tiempo. Aprenderás cómo encontrar funciones lentas, fugas de memoria y algoritmos ineficientes. El artículo será útil tanto para principiantes como para desarrolladores iOS experimentados.
Herramienta n.º 1: Xcode Instruments — tu asistente principal
Xcode Instruments es un potente conjunto de perfiladores integrado en Xcode. La herramienta más popular es Time Profiler. Muestra cuánto tiempo de CPU gasta cada función. Se inicia a través del menú Product > Profile o con la combinación Cmd + I.
Cómo usar Time Profiler
- Inicia el perfilamiento de tu aplicación.
- Realiza la acción que parece lenta (scroll, carga de datos, animación).
- Detén la grabación y observa la pila de llamadas.
- Encuentra las funciones con mayor tiempo de ejecución (columnas rojas).
Ejemplo: si tu tableView(_:cellForRowAt:) ocupa el 80% del tiempo, el problema está ahí. Un doble clic mostrará las líneas de código específicas.
Ejemplo de código para perfilamiento
Supongamos que tienes una función de carga de imágenes:
func loadImage(from url: URL) -> UIImage? { guard let data = try? Data(contentsOf: url) else { return nil } return UIImage(data: data)}Time Profiler mostrará que Data(contentsOf:) bloquea el hilo principal. La solución es la carga asíncrona mediante URLSession.
Herramienta n.º 2: os_signpost — perfilamiento puntual
A veces es necesario medir solo una sección de código sin ensuciar el perfil general. Para esto usa os_signpost, una API ligera para marcar eventos. Los datos se incorporan automáticamente en Instruments.
Configuración de os_signpost
Primero importa os y crea un logger:
import os
let log = OSLog(subsystem: "com.yourapp.network", category: "Loading")Luego envuelve el código a medir:
os_signpost(.begin, log: log, name: "FetchData")// Tu código de cargaos_signpost(.end, log: log, name: "FetchData")En Instruments selecciona la plantilla os_signpost — verás el tiempo exacto de ejecución de cada bloque. Esto es indispensable al comparar dos algoritmos.
Ejemplo real: comparación de ordenamientos
Comparemos el ordenamiento estándar y el manual:
os_signpost(.begin, log: log, name: "SortBuiltin")let sortedBuiltin = largeArray.sorted()os_signpost(.end, log: log, name: "SortBuiltin")
os_signpost(.begin, log: log, name: "SortManual")let sortedManual = largeArray.sorted { $0 < $1 }os_signpost(.end, log: log, name: "SortManual")El resultado a menudo sorprende: el ordenamiento integrado es 30-40% más rápido que el manual.
Herramienta n.º 3: Medición manual de tiempo con DispatchTime
Para comprobaciones rápidas sin Instruments usa DispatchTime. Es una forma sencilla de medir el tiempo de ejecución de una función directamente en el código.
Ejemplo de medición
let start = DispatchTime.now()// Bloque a medirlet result = heavyComputation()let end = DispatchTime.now()
let nanoTime = end.uptimeNanoseconds - start.uptimeNanosecondslet timeInterval = Double(nanoTime) / 1_000_000_000print("Tiempo de ejecución: \\(timeInterval) s")