El loadmensaje
El tiempo de ejecución envía el loadmensaje a cada objeto de clase, muy poco después de que el objeto de clase se cargue en el espacio de direcciones del proceso. Para las clases que forman parte del archivo ejecutable del programa, el tiempo de ejecución envía el loadmensaje muy temprano en la vida útil del proceso. Para las clases que están en una biblioteca compartida (cargada dinámicamente), el tiempo de ejecución envía el mensaje de carga justo después de que la biblioteca compartida se cargue en el espacio de direcciones del proceso.
Además, el tiempo de ejecución solo envía loada un objeto de clase si ese objeto de clase en sí mismo implementa el loadmétodo. Ejemplo:
@interface Superclass : NSObject
@end
@interface Subclass : Superclass
@end
@implementation Superclass
+ (void)load {
NSLog(@"in Superclass load");
}
@end
@implementation Subclass
// ... load not implemented in this class
@end
El tiempo de ejecución envía el loadmensaje al Superclassobjeto de clase. No , no enviar el loadmensaje al Subclassobjeto de clase, a pesar de que Subclasshereda el método de Superclass.
El tiempo de ejecución envía el loadmensaje a un objeto de clase después de haber enviado el loadmensaje a todos los objetos de superclase de la clase (si esos objetos de superclase se implementan load) y todos los objetos de clase en las bibliotecas compartidas a las que se vincula. Pero aún no sabe qué otras clases de su propio ejecutable han recibido load.
Cada clase que su proceso carga en su espacio de direcciones recibirá un loadmensaje, si implementa el loadmétodo, independientemente de si su proceso hace algún otro uso de la clase.
Puede ver cómo el tiempo de ejecución busca el loadmétodo como un caso especial en _class_getLoadMethodof objc-runtime-new.mmy lo llama directamente desde call_class_loadsadentro objc-loadmethod.mm.
El tiempo de ejecución también ejecuta el loadmétodo de cada categoría que carga, incluso si se implementan varias categorías en la misma clase load. Esto es inusual. Normalmente, si dos categorías definen el mismo método en la misma clase, uno de los métodos "ganará" y se usará, y el otro método nunca será llamado.
El initializemétodo
El tiempo de ejecución llama al initializemétodo en un objeto de clase justo antes de enviar el primer mensaje (distinto de loado initialize) al objeto de clase o cualquier instancia de la clase. Este mensaje se envía utilizando el mecanismo normal, por lo que si su clase no se implementa initialize, pero hereda de una clase que lo hace, entonces su clase usará su superclase initialize. El tiempo de ejecución enviará initializeprimero a todas las superclases de una clase (si las superclases aún no se han enviado initialize).
Ejemplo:
@interface Superclass : NSObject
@end
@interface Subclass : Superclass
@end
@implementation Superclass
+ (void)initialize {
NSLog(@"in Superclass initialize; self = %@", self);
}
@end
@implementation Subclass
// ... initialize not implemented in this class
@end
int main(int argc, char *argv[]) {
@autoreleasepool {
Subclass *object = [[Subclass alloc] init];
}
return 0;
}
Este programa imprime dos líneas de salida:
2012-11-10 16:18:38.984 testApp[7498:c07] in Superclass initialize; self = Superclass
2012-11-10 16:18:38.987 testApp[7498:c07] in Superclass initialize; self = Subclass
Dado que el sistema envía el initializemétodo con pereza, una clase no recibirá el mensaje a menos que su programa realmente envíe mensajes a la clase (o una subclase, o instancias de la clase o subclases). Y para cuando lo reciba initialize, todas las clases en su proceso ya deberían haber recibido load(si corresponde).
La forma canónica de implementar initializees esta:
@implementation Someclass
+ (void)initialize {
if (self == [Someclass class]) {
// do whatever
}
}
El objetivo de este patrón es evitar Someclassreinicializarse cuando tiene una subclase que no se implementa initialize.
El motor de ejecución envía el initializemensaje en la _class_initializefunción en formato objc-initialize.mm. Puede ver que utiliza objc_msgSendpara enviarlo, que es la función normal de envío de mensajes.
Otras lecturas
Consulte las preguntas y respuestas del viernes de Mike Ash sobre este tema.
+loadse envía por separado para categorías; es decir, cada categoría de una clase puede contener su propio+loadmétodo.initializese invocará correctamente mediante unloadmétodo, si es necesario, debido a laloadreferencia a la entidad no inicializada. ¡Esto puede (extrañamente, pero sensiblemente) llevar ainitializecorrer antesload! Eso es lo que he observado, de todos modos. Esto parece ser contrario a "Y para cuando lo recibainitialize, todas las clases en su proceso ya deberían haber recibidoload(si corresponde)".loadprimero. A continuación, puede recibirinitializemientrasloadaún se está ejecutando.Lo que significa es no anular
+initializeen una categoría, probablemente romperá algo.+loadse llama una vez por clase o categoría que implementa+load, tan pronto como se carga esa clase o categoría. Cuando dice "vinculado estáticamente" significa compilado en el binario de su aplicación. Los+loadmétodos de las clases así compiladas se ejecutarán cuando se inicie la aplicación, probablemente antes de que entremain(). Cuando dice "cargado dinámicamente", significa cargado a través de paquetes de complementos o una llamada adlopen(). Si está en iOS, puede ignorar ese caso.+initializese llama la primera vez que se envía un mensaje a la clase, justo antes de que maneje ese mensaje. Esto (obviamente) solo ocurre una vez. Si anula+initializeen una categoría, sucederá una de estas tres cosas:Esta es la razón por la que nunca debe anular
+initializeuna categoría; de hecho, es bastante peligroso intentar reemplazar cualquier método en una categoría porque nunca está seguro de qué está reemplazando o si su propio reemplazo será reemplazado por otra categoría.Por cierto, otro tema a considerar
+initializees que si alguien te subclasifica, potencialmente te llamarán una vez para tu clase y una vez para cada subclase. Si está haciendo algo como configurarstaticvariables, querrá protegerse contra eso: ya sea condispatch_once()o mediante pruebasself == [MyClass class].fuente