¿Por qué el código de method swizzling siempre está dentro de +load? ¿No se puede poner en el parecido +initialize?
+load y +initialize parecen métodos que se «invocan una vez cuando la clase está preparada», pero difieren por completo en el momento y la forma de invocación y en las reglas de herencia. También es un tema habitual en entrevistas de Objective-C y, si se entiende mal, es fácil acabar preguntándose «¿por qué este código se ejecuta dos veces?». Merece la pena resumirlo.
Tabla comparativa rápida
+load |
+initialize |
|
|---|---|---|
| Momento de invocación | Cuando el runtime carga la clase (antes de main) | Justo antes de que la clase reciba su primer mensaje (lazy) |
| Forma de invocación | Llamada directa mediante puntero a función | A través de objc_msgSend |
| Categorías | Las implementaciones de la clase y la categoría se invocan por separado | La implementación de la categoría sobrescribe la de la clase |
| Herencia | Solo se invoca la clase que lo implementa | Se hereda; puede invocarse varias veces por las subclases |
| ¿Si no se usa? | Se invoca igualmente | Nunca se invoca si no se usa la clase |
Veamos paso a paso los puntos que no resultan evidentes solo con la tabla.
+load: antes de main, siempre
+load se invoca cuando el runtime carga el binario que contiene la clase o categoría, es decir, antes de que se ejecute la función main. Se invoca aunque la aplicación nunca use esa clase.
Su forma de invocación es peculiar. Se llama directamente mediante un puntero a función, sin pasar por objc_msgSend. Por eso no se aplican las reglas habituales de sobrescritura.
- Si una subclase no implementa
+load, la implementación del padre no se invoca en su lugar. - La
+loadde la clase y la+loadde la categoría se invocan por separado; no se sobrescriben. - El orden está garantizado: la
+loadde la clase padre antes que la de la hija, y la+loadde la clase antes que la de la categoría.
Estas propiedades encajan exactamente con el swizzling. Aunque +load se implemente en una categoría, no interfiere con la +load de la clase original y se ejecuta una vez de forma fiable en el momento más temprano de la vida de la aplicación.
Pero tiene un coste. +load se carga directamente al tiempo de inicio de la aplicación. Como la +load de todas las clases se ejecuta secuencialmente antes de main, el trabajo pesado aquí retrasa la primera pantalla. Por eso Apple recomienda desde hace años «evitar +load siempre que sea posible». De hecho, dentro de +load es posible que aún no se hayan cargado otras clases fuera de la imagen a la que pertenece self, lo que limita las operaciones disponibles.
+initialize: justo antes del primer mensaje, de forma perezosa
+initialize adopta la estrategia opuesta. El runtime lo invoca justo antes de que la clase reciba su primer mensaje. Si la aplicación nunca usa la clase, nunca se invoca. Es un punto de inicialización lazy sin coste para el tiempo de inicio.
Como pasa por objc_msgSend, se aplican las reglas de herencia como en un método normal. Aquí aparece la famosa trampa.
@implementation Animal
+ (void)initialize {
NSLog(@"initialize: %@", self);
}
@end
@interface Dog : Animal
@end
@implementation Dog
@end
Al enviar el primer mensaje a Dog, el registro queda así.
initialize: Animal
initialize: Dog
El +initialize de Animal se ejecuta dos veces: una por Animal y otra porque Dog, que no implementa +initialize, hereda y ejecuta la implementación del padre. Por eso la implementación convencional de +initialize incluye una comprobación de clase.
+ (void)initialize {
if (self == [Animal class]) {
// Solo la inicialización que corresponde realmente a Animal
}
}
Como referencia, +initialize es seguro para subprocesos por sí mismo, ya que el runtime bloquea por clase antes de invocarlo. No hace falta añadir dispatch_once.
Criterios prácticos de elección
El criterio es sencillo.
- Tareas que deben ejecutarse «siempre y lo antes posible», como el swizzling o el registro de clases →
+load(con una implementación mínima) - Preparación necesaria solo cuando se usa la clase →
+initialize(la comprobación de self es obligatoria) - La mayoría de las inicializaciones → en realidad ninguna de las dos; basta con un
dispatch_oncesingleton o una propiedad lazy
Swift no tiene este dilema. Swift no ofrece nada equivalente a +load. Tampoco existe un mecanismo oficial para insertar código ejecutable global antes de main. En su lugar, las propiedades de tipo (static let) garantizan lazy y seguridad de subprocesos a nivel del lenguaje, sustituyendo el papel de +initialize. Es una de las ventajas estructurales de Swift para el rendimiento de inicio de la aplicación.
Resumen
+loadse invoca antes de main, siempre y directamente mediante un puntero a función; se invocan por separado las implementaciones de la clase y la categoría+initializese invoca justo antes del primer mensaje, de forma lazy y mediante msgSend; nunca se invoca en clases no utilizadas+initializepuede ejecutarse varias veces por la herencia, por lo que laif (self == [MyClass class])comprobación es convencional- Por qué el swizzling vive en
+load: momento más temprano + llamada independiente de la categoría + ejecución única garantizada - Abusar de
+loadretrasa directamente el inicio de la aplicación; la inicialización pesada debe posponerse con lazy
Al leerlo junto con otros artículos de la serie sobre el runtime (objc_msgSend, reenvío de mensajes, swizzling y KVO), se ve que la diferencia entre +load y +initialize acaba separándose en un único eje: si la llamada pasa por msgSend. Al entender la ruta de invocación, ya no hace falta memorizar las reglas.

