objective c - framework - NSObject+cargar e+inicializar-¿Qué hacen?
swift ios documentation (2)
El mensaje de load
El tiempo de ejecución envía el mensaje de load
a cada objeto de clase, muy pronto después de que el objeto de clase se carga 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 mensaje de load
muy temprano en la vida 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 carga en el espacio de direcciones del proceso.
Además, el tiempo de ejecución solo envía load
a un objeto de clase si ese objeto de clase implementa el método de load
. 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 mensaje de load
al objeto de clase Superclass
. No envía el mensaje de load
al objeto de clase Subclass
, aunque Subclass
hereda el método de Superclass
.
El tiempo de ejecución envía el mensaje de load
a un objeto de clase después de enviar el mensaje de load
a todos los objetos de superclase de la clase (si esos objetos de superclase implementan load
) y todos los objetos de clase en bibliotecas compartidas a las que se vincula. Pero no sabe qué otras clases en su propio ejecutable han recibido load
todavía.
Cada clase que su proceso carga en su espacio de direcciones recibirá un mensaje de load
, si implementa el método de load
, independientemente de si su proceso hace algún otro uso de la clase.
Puede ver cómo el tiempo de ejecución busca el método load
como un caso especial en el _class_getLoadMethod
de objc-runtime-new.mm
, y lo llama directamente desde call_class_loads
en objc-loadmethod.mm
.
El tiempo de ejecución también ejecuta el método de load
de cada categoría que carga, incluso si varias categorías en la misma clase implementan 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 nunca se llamará al otro método.
El método de initialize
El tiempo de ejecución llama al método de initialize
en un objeto de clase justo antes de enviar el primer mensaje (que no sea load
o 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 implementa initialize
, sino que hereda de una clase que sí lo hace, su clase usará la initialize
su superclase. El tiempo de ejecución enviará la initialize
a todas las superclases de una clase primero (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 método de initialize
forma perezosa, 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 reciba la initialize
, todas las clases en su proceso ya deberían haber recibido load
(si corresponde).
La forma canónica de implementar initialize
es esta:
@implementation Someclass
+ (void)initialize {
if (self == [Someclass class]) {
// do whatever
}
}
El objetivo de este patrón es evitar que Someclass
a sí mismo cuando tenga una subclase que no implemente la initialize
.
El tiempo de ejecución envía el mensaje de initialize
en la función objc-initialize.mm
en objc-initialize.mm
. Puede ver que usa objc_msgSend
para enviarlo, que es la función normal de envío de mensajes.
Otras lecturas
Eche un vistazo a las preguntas y respuestas del viernes de Mike Ash sobre este tema.
Estoy interesado en comprender las circunstancias que llevan a un desarrollador a anular + inicializar o + cargar. La documentación deja en claro que el tiempo de ejecución de Objective-C llama a estos métodos, pero eso es todo lo que queda claro en la documentación de esos métodos. :-)
Mi curiosidad proviene de mirar el código de ejemplo de Apple: MVCNetworking. Su clase de modelo tiene un método de +(void) applicationStartup
. Realiza algunas tareas domésticas en el sistema de archivos, lee NSDefaults, etc ... y, después de tratar de asimilar los métodos de clase de NSObject, parece que este trabajo de limpieza podría estar bien en + carga.
Modifiqué el proyecto MVCNetworking, eliminando la llamada en App Delegate to + applicationStartup, y poniendo los bits de housekeeping en + load ... mi computadora no se incendió, ¡pero eso no significa que sea correcta! Espero obtener una comprensión de las sutilezas, las trampas, y las cosas alrededor de un método de configuración personalizada que tiene que llamar frente a + cargar o + inicializar.
Para + cargar documentación dice:
El mensaje de carga se envía a clases y categorías que están cargadas dinámicamente y vinculadas estáticamente, pero solo si la clase o categoría recientemente cargada implementa un método que puede responder.
Esta oración es grosera y difícil de analizar si no se conoce el significado preciso de todas las palabras. ¡Ayuda!
¿Qué se entiende por "ambos dinámicamente cargados y estáticamente vinculados?" ¿Se puede cargar algo dinámicamente Y estáticamente vinculado, o son mutuamente excluyentes?
"... la clase o categoría recién cargada implementa un método que puede responder" ¿Qué método? Responde como?
En cuanto a + initialize, la documentación dice:
Inicializarlo se invoca solo una vez por clase. Si desea realizar una inicialización independiente para la clase y para las categorías de la clase, debe implementar métodos de carga.
Considero que esto significa que "si estás intentando configurar la clase ... no uses initialize". Bien vale. ¿Cuándo o por qué anularía la inicialización?
Lo que significa es que no anula +initialize
en una categoría, probablemente romperá algo.
+load
se llama una vez por clase o categoría que implementa +load
, tan pronto como esa clase o categoría se carga. Cuando dice "estáticamente vinculado", significa compilado en el archivo binario de tu aplicación. Los métodos de +load
en las clases así compiladas se ejecutarán cuando se inicie su aplicación, probablemente antes de que entre en main()
. Cuando dice "cargada dinámicamente", significa cargado a través de paquetes de complementos o una llamada a dlopen()
. Si tienes iOS, puedes ignorar ese caso.
+initialize
se 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 +initialize
en una categoría, ocurrirá una de estas tres cosas:
- se llama a su implementación de categoría y la implementación de la clase no
- se llama a la implementación de la categoría de otra persona; nada de lo que escribiste
- su categoría aún no se ha cargado y su implementación nunca se llama.
Esta es la razón por la que nunca debe anular +initialize
en una categoría; de hecho, es bastante peligroso intentar reemplazar cualquier método en una categoría porque nunca está seguro de lo que está reemplazando o si su propio reemplazo será reemplazado por otro categoría.
Por cierto, otro tema a considerar con +initialize
es que si alguien te subclasifica, posiblemente te llamarán una vez para tu clase y una vez para cada subclase. Si está haciendo algo así como configurar variables static
, querrá evitar eso: ya sea con dispatch_once()
o probando self == [MyClass class]
.