Objective-C Language
Entorno de tiempo de ejecución de bajo nivel
Buscar..
Observaciones
Para utilizar el tiempo de ejecución de Objective-C, debe importarlo.
#import <objc/objc.h>
Adjuntar objeto a otro objeto existente (asociación)
Es posible adjuntar un objeto a un objeto existente como si hubiera una nueva propiedad. Esto se denomina asociación y permite extender objetos existentes. Puede usarse para proporcionar almacenamiento cuando se agrega una propiedad a través de una extensión de clase o, de lo contrario, se agrega información adicional a un objeto existente.
El tiempo de ejecución libera automáticamente el objeto asociado una vez que se desasigna el objeto de destino.
#import <objc/runtime.h>
// "Key" for association. Its value is never used and doesn't
// matter. The only purpose of this global static variable is to
// provide a guaranteed unique value at runtime: no two distinct
// global variables can share the same address.
static char key;
id target = ...;
id payload = ...;
objc_setAssociateObject(target, &key, payload, OBJC_ASSOCIATION_RETAIN);
// Other useful values are OBJC_ASSOCIATION_COPY
// and OBJ_ASSOCIATION_ASSIGN
id queryPayload = objc_getAssociatedObject(target, &key);
Métodos de aumento utilizando el método Swizzling
El tiempo de ejecución de Objective-C le permite cambiar la implementación de un método en tiempo de ejecución. Esto se llama método swizzling y se usa a menudo para intercambiar las implementaciones de dos métodos. Por ejemplo, si se intercambian los métodos foo
y bar
, el envío del mensaje foo
ejecutará la implementación de bar
y viceversa.
Esta técnica se puede utilizar para aumentar o "parchar" los métodos existentes que no puede editar directamente, como los métodos de las clases proporcionadas por el sistema.
En el siguiente ejemplo, el método -[NSUserDefaults synchronize]
se aumenta para imprimir el tiempo de ejecución de la implementación original.
IMPORTANTE: muchas personas intentan hacer swizzling usando method_exchangeImplementations
. Sin embargo, este enfoque es peligroso si necesita llamar al método que está reemplazando, porque lo estará usando con un selector diferente del que espera recibir. Como resultado, su código puede dividirse de formas extrañas e inesperadas, especialmente si varias partes dominan un objeto de esta manera. En su lugar, siempre debe hacer swizzling usando setImplementation
junto con una función C, permitiéndole llamar al método con el selector original.
#import "NSUserDefaults+Timing.h"
#import <objc/runtime.h> // Needed for method swizzling
static IMP old_synchronize = NULL;
static void new_synchronize(id self, SEL _cmd);
@implementation NSUserDefaults(Timing)
+ (void)load
{
Method originalMethod = class_getInstanceMethod([self class], @selector(synchronize:));
IMP swizzleImp = (IMP)new_synchronize;
old_synchronize = method_setImplementation(originalMethod, swizzleImp);
}
@end
static void new_synchronize(id self, SEL _cmd);
{
NSDate *started;
BOOL returnValue;
started = [NSDate date];
// Call the original implementation, passing the same parameters
// that this function was called with, including the selector.
returnValue = old_synchronize(self, _cmd);
NSLog(@"Writing user defaults took %f seconds.", [[NSDate date] timeIntervalSinceDate:started]);
return returnValue;
}
@end
Si necesita cambiar un método que toma parámetros, simplemente los agrega como parámetros adicionales a la función. Por ejemplo:
static IMP old_viewWillAppear_animated = NULL;
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated);
...
Method originalMethod = class_getClassMethod([UIViewController class], @selector(viewWillAppear:));
IMP swizzleImp = (IMP)new_viewWillAppear_animated;
old_viewWillAppear_animated = method_setImplementation(originalMethod, swizzleImp);
...
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated)
{
...
old_viewWillAppear_animated(self, _cmd, animated);
...
}
Métodos de llamada directamente
Si necesita llamar a un método Objective-C desde el código C, tiene dos formas: usar objc_msgSend
u obtener el IMP
(puntero de la función de implementación del método) y llamar a eso.
#import <objc/objc.h>
@implementation Example
- (double)negate:(double)value {
return -value;
}
- (double)invert:(double)value {
return 1 / value;
}
@end
// Calls the selector on the object. Expects the method to have one double argument and return a double.
double performSelectorWithMsgSend(id object, SEL selector, double value) {
// We declare pointer to function and cast `objc_msgSend` to expected signature.
// WARNING: This step is important! Otherwise you may get unexpected results!
double (*msgSend)(id, SEL, double) = (typeof(msgSend)) &objc_msgSend;
// The implicit arguments of self and _cmd need to be passed in addition to any explicit arguments.
return msgSend(object, selector, value);
}
// Does the same as the above function, but by obtaining the method's IMP.
double performSelectorWithIMP(id object, SEL selector, double value) {
// Get the method's implementation.
IMP imp = class_getMethodImplementation([self class], selector);
// Cast it so the types are known and ARC can work correctly.
double (*callableImp)(id, SEL, double) = (typeof(callableImp)) imp;
// Again, you need the explicit arguments.
return callableImp(object, selector, value);
}
int main() {
Example *e = [Example new];
// Invoke negation, result is -4
double x = performSelectorWithMsgSend(e, @selector(negate:), 4);
// Invoke inversion, result is 0.25
double y = performSelectorWithIMP(e, @selector(invert:), 4);
}
objc_msgSend
trabaja obteniendo el IMP para el método y llamando a eso. Los IMP
para los últimos métodos llamados se almacenan en caché, por lo que si está enviando un mensaje de Objective-C en un bucle muy cerrado, puede obtener un rendimiento aceptable. En algunos casos, el almacenamiento en caché manual del IMP puede ofrecer un rendimiento ligeramente mejor, aunque esta es una optimización de último recurso.