Method Swizzling es una técnica de runtime en la que las implementaciones de dos métodos de clase se intercambian durante la ejecución. Permite sobrescribir o complementar el comportamiento de un método del sistema sin crear una subclase ni modificar el código fuente. La técnica ha encontrado su mayor aplicación en el desarrollo iOS con Objective-C, pero existen análogos en Kotlin/Android mediante reflection. Según NSHipster Guide by Mattt, 2024, el swizzling es uno de los mecanismos más potentes, pero también más peligrosos del Objective-C Runtime.
Puntos clave
Method Swizzling es una técnica de runtime que intercambia las implementaciones de dos métodos de Objective-C. Después del swizzling, llamar a originalSelector ejecuta el código de swizzledSelector, y viceversa. Esto es posible gracias a la arquitectura de Objective-C Runtime, donde cada selector (SEL) está asociado con una implementación (IMP) a través de una dispatch table — una tabla que puede modificarse durante la ejecución.
El término “swizzling” fue introducido en la comunidad de desarrolladores Cocoa a principios de los años 2000. La técnica ganó gran reconocimiento gracias a librerías como: AFNetworking (swizzling de UIWebView para rastrear la carga), Aspects (framework AOP basado en swizzling) y FLEX (herramienta de depuración que swizzlea métodos del sistema para inspección). Hoy en día, el swizzling se usa implícitamente en la mayoría de las aplicaciones iOS — a través de librerías de monitoreo y analítica.
Una propiedad importante del swizzling es la globalidad: el reemplazo de la implementación ocurre a nivel de clase, no de instancia. Si una librería swizzlea el método UIViewController.viewDidLoad, afecta a TODAS las instancias de UIViewController en la aplicación, incluidas las del sistema. Esto es tanto la fortaleza del swizzling — una línea de código cambia el comportamiento de toda la aplicación — como la principal fuente de errores.
Objective-C Runtime almacena en cada clase una dispatch table — un diccionario donde la clave es SEL (identificador del método) y el valor es IMP (puntero a la función de implementación). Cuando una aplicación envía un mensaje a un objeto, objc_msgSend realiza una búsqueda lineal en esta tabla. Method Swizzling reemplaza el IMP de un SEL con el IMP de otro SEL, redirigiendo las llamadas.
// Implementación segura de method swizzling
@implementation NSObject (SafeSwizzle)
+ (void)swizzleClassMethod:(SEL)original
with:(SEL)swizzled {
Class cls = [self class];
SEL originalSel = original;
SEL swizzledSel = swizzled;
Method originalMethod = class_getInstanceMethod(cls, originalSel);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
@end
La función clave es method_exchangeImplementations(Method, Method). Intercambia atómicamente los IMP de dos objetos Method. Después de la llamada, la dispatch table de la clase se modifica: al llamar a original se ejecuta el código swizzled, al llamar a swizzled se ejecuta el código original. La categoría SafeSwizzle añade este método a todos los NSObject, permitiendo que cualquier clase realice swizzling.
Una implementación segura de swizzling requiere llamar a la implementación original dentro de la versión swizzled. De lo contrario, el comportamiento original del método se pierde irreversiblemente. El patrón correcto es guardar el IMP original antes del intercambio y llamarlo en el método swizzled:
// Swizzling con llamada a la implementación original
- (void)swizzled_viewDidLoad {
// 1. Llamada a la implementación original
[self swizzled_viewDidLoad];
// 2. Lógica adicional después de la llamada original
NSLog("viewDidLoad ejecutado, swizzling activo");
}
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleClassMethod:@selector(viewDidLoad)
with:@selector(swizzled_viewDidLoad)];
});
}
dispatch_once garantiza que el swizzling se ejecute exactamente una vez durante la vida de la aplicación. Volver a swizzlear el mismo método provocaría una recursión infinita: el método swizzled se llamaría a sí mismo. +load se invoca cuando la clase se carga en el runtime — es un punto seguro para el swizzling que se ejecuta antes del código principal de la aplicación.
La dispatch table de una clase Objective-C es un array de estructuras method_t que contienen SEL, IMP y tipo de retorno. method_exchangeImplementations simplemente intercambia dos punteros IMP en esta tabla. Importante: el swizzling funciona solo a nivel de clase, no de protocolo. Si un método está definido en un protocolo pero no implementado, la dispatch table no contiene una entrada para swizzling.
La sobrecarga del swizzling es mínima — intercambiar dos punteros IMP en la dispatch table toma unos nanosegundos. Después del swizzling, la llamada al método no se ralentiza: objc_msgSend encuentra el IMP en el mismo tiempo O(1) que antes del swizzling. La única operación adicional es una verificación de la caché de métodos en la primera llamada después del intercambio. Según datos de Apple Performance Team, el swizzling no afecta al rendimiento de la aplicación.
Method Swizzling se utiliza en tres escenarios principales: monitoreo y analítica (seguimiento de viewDidLoad, viewDidAppear para el envío automático de eventos), interceptación AOP (registro de parámetros de todas las llamadas a métodos) y hotfix (corrección de un error en producción sin revisión de App Store mediante librerías como JSPatch).
Cada uno de estos escenarios funciona porque el swizzling se aplica centralizadamente. Una librería de analítica realiza el swizzling una vez en +load, y todas las instancias de UIViewController en la aplicación comienzan a enviar eventos. El desarrollador no necesita añadir código en cada controlador — esto reduce la duplicación y el riesgo de errores.
En Android, el method swizzling en el sentido clásico de Objective-C es imposible — Java/Kotlin utilizan despachación estática mediante vtable. Sin embargo, existen mecanismos que logran un efecto similar: Java Reflection para reemplazar implementaciones en runtime y Gradle Transform API / ASM para modificar el bytecode en tiempo de compilación.
// Swizzling en Android mediante reflection + companion object
class Logger {
companion object {
var originalImpl: (() -> Unit)? = null
}
fun log() {
println("log original")
}
}
// Reemplazo de implementación en runtime mediante reflection
fun swizzleLog() {
val originalMethod = Logger::class.java
.getDeclaredMethod("log")
originalMethod.isAccessible = true
Logger.originalImpl = {
originalMethod.invoke(Logger())
}
// Sustitución mediante función inline
println("swizzled: log interceptado")
}
Este código reemplaza el comportamiento del método log() mediante Java Reflection: getDeclaredMethod accede a la implementación privada, isAccessible desactiva las comprobaciones de acceso. En lugar de llamar directamente a log(), se llama a un envoltorio que ejecuta lógica adicional. Sin embargo, Android optimiza los métodos calientes mediante JIT — la reflection puede no funcionar en segmentos AOT ya compilados.
Un enfoque más fiable es la manipulación de bytecode a través de Gradle Transform API o AGP (Android Gradle Plugin) con la librería ASM. La modificación del bytecode se realiza en tiempo de compilación: ASM añade llamadas a cada método de la clase. Así funcionan las herramientas de cobertura de código (JaCoCo) y de monitoreo de rendimiento (Firebase Performance Monitoring).
Method Swizzling es una técnica de alto riesgo. Conflictos entre librerías: si dos librerías swizzlean el mismo método, el orden de ejecución no está garantizado. Incompatibilidad con actualizaciones de iOS: si Apple cambia la firma o elimina un método en una nueva versión de iOS, el swizzling provoca fallos. Falta de visibilidad en el código: el swizzling no es visible en la implementación de la clase, lo que dificulta la depuración.
| Riesgo | Descripción | Mitigación |
|---|---|---|
| Conflicto de librerías | Dos librerías swizzlean viewDidAppear — una rompe a la otra | Verificar si el método ya está swizzled mediante class_getInstanceMethod |
| Recursión | Volver a swizzlear el mismo método provoca un bucle infinito | Usar siempre dispatch_once |
| Cambio de firma | Apple cambia la firma del método en una nueva versión de iOS — IMP no coincide | Probar en todas las versiones de iOS compatibles |
| Invisibilidad | El swizzling no aparece en la pila de llamadas de Xcode | Documentar todas las operaciones de swizzling en el código |
| App Review | Apple rechaza aplicaciones con swizzling no documentado | Usar solo APIs públicas y documentar el propósito |
Las mejores prácticas para un swizzling seguro incluyen: llamar siempre a la implementación original, realizar el swizzling estrictamente en +load mediante dispatch_once, nombrar los métodos swizzled con un prefijo (por ejemplo, s_originalMethodName), documentar cada operación de swizzling con su propósito. La librería Aspects resuelve el problema de conflictos mediante la ejecución encadenada de bloques antes/después del método original.
Las alternativas al method swizzling son preferibles para el código de producción por su previsibilidad y seguridad. Los delegados y protocolos (UIApplicationDelegate, UITableViewDelegate) proporcionan puntos de extensión explícitos sin modificar el runtime. La subclasificación — crear una subclase de UIViewController sobrescribiendo viewDidAppear — funciona de forma predecible y no tiene conflictos.
SwiftUI y Combine eliminan la necesidad de swizzling: los modificadores (onAppear, onChange) añaden comportamiento de forma declarativa, sin sobrescribir métodos. En Android Jetpack Compose logra lo mismo mediante efectos (LaunchedEffect, SideEffect) y modificadores. Los frameworks AOP (AspectJ para Android, InterposeKit para iOS) proporcionan una alternativa segura con weaving en tiempo de compilación.
Según datos de Apple WWDC 2024, el runtime de Swift no soporta method swizzling a nivel de lenguaje — los métodos @objc dynamic solo pueden ser swizzled a través de Objective-C Runtime. Las aplicaciones Swift que no usan @objc están completamente protegidas contra swizzling accidental por librerías de terceros. Esto hace que Swift sea más seguro, pero limita las capacidades de instrumentación en runtime.
Los modificadores de SwiftUI (onAppear, onChange, onReceive) y los efectos de Jetpack Compose (LaunchedEffect, SideEffect, DisposableEffect) reemplazan completamente el swizzling para tareas de UI. Proporcionan una forma declarativa, predecible y comprobable de añadir comportamiento transversal sin modificar la dispatch table. En proyectos nuevos, Apple y Google recomiendan este enfoque en lugar de la interceptación en runtime.
Preguntas frecuentes
Method Swizzling es aceptable para producción si se siguen las reglas: dispatch_once para ejecución única, llamada a la implementación original, pruebas en todas las versiones de iOS y documentación. Para tareas sencillas, es mejor usar delegados o subclasificación. El swizzling en producción está justificado para librerías de monitoreo y analítica.
Method Swizzling es una técnica específica para reemplazar IMP en la dispatch table. AOP (Programación Orientada a Aspectos) es un paradigma en el que el swizzling puede usarse como uno de los mecanismos. AOP también incluye weaving en tiempo de compilación (AspectJ), interceptación basada en proxies (Spring AOP) y generación de código.
Usar un breakpoint en objc_msgSend para rastrear todos los mensajes. Añadir un breakpoint simbólico en method_exchangeImplementations con una condición sobre el nombre de la clase. La herramienta FLEX muestra qué métodos de la clase están swizzled. Para una verificación sistemática, usar un script de lldb que muestre la dispatch table de la clase.
Swift no soporta swizzling a nivel de lenguaje. Method Swizzling solo funciona para métodos marcados con @objc dynamic, que se compilan a través de Objective-C Runtime. Los métodos Swift puros (sin @objc) usan despachación estática y no pueden ser swizzled — su dispatch table no está accesible para modificación.
Firebase Analytics (swizzling de viewDidAppear para seguimiento automático de pantallas), Amplitude, Mixpanel, FLEX (inspección de UI), OHHTTPStubs (simulación de solicitudes de red), Aspects (framework AOP). Todas realizan swizzling en +load mediante dispatch_once con llamada a la implementación original.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también