Objective-C Language
Środowisko wykonawcze niskiego poziomu
Szukaj…
Uwagi
Aby użyć środowiska wykonawczego Objective-C, musisz go zaimportować.
#import <objc/objc.h>
Dołącz obiekt do innego istniejącego obiektu (powiązanie)
Możliwe jest dołączenie obiektu do istniejącego obiektu, tak jakby istniała nowa właściwość. To się nazywa asocjacja i pozwala rozszerzyć istniejące obiekty. Może służyć do przechowywania danych podczas dodawania właściwości za pomocą rozszerzenia klasy lub w inny sposób dodawania dodatkowych informacji do istniejącego obiektu.
Powiązany obiekt jest automatycznie zwalniany przez środowisko wykonawcze po zwolnieniu obiektu docelowego.
#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);
Metody rozszerzania za pomocą metody Swizzling
Środowisko wykonawcze Objective-C umożliwia zmianę implementacji metody w czasie wykonywania. Nazywa się to metodą swizzling i jest często używane do wymiany implementacji dwóch metod. Na przykład, jeśli zostaną wymienione metody foo
i bar
, wysłanie komunikatu foo
spowoduje teraz wykonanie implementacji bar
i vice versa.
Techniki tej można używać do rozszerzania lub „łatania” istniejących metod, których nie można edytować bezpośrednio, takich jak metody klas systemowych.
W poniższym przykładzie rozszerzono metodę -[NSUserDefaults synchronize]
, aby wydrukować czas wykonania oryginalnej implementacji.
WAŻNE: Wiele osób próbuje zrobić swizzling przy użyciu method_exchangeImplementations
. Jednak takie podejście jest niebezpieczne, jeśli musisz wywołać metodę, którą zastępujesz, ponieważ będziesz wywoływał ją za pomocą innego selektora niż oczekuje. W rezultacie Twój kod może się zepsuć w dziwny i nieoczekiwany sposób - szczególnie, jeśli wiele podmiotów zamienia obiekt w ten sposób. Zamiast tego powinieneś zawsze robić zamiatanie za pomocą setImplementation
w połączeniu z funkcją C, pozwalając na wywołanie metody z oryginalnym selektorem.
#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
Jeśli chcesz zamienić metodę, która pobiera parametry, po prostu dodajesz je jako dodatkowe parametry do funkcji. Na przykład:
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);
...
}
Bezpośrednie wywoływanie metod
Jeśli potrzebujesz wywołać metodę Objective-C z kodu C, masz dwa sposoby: użycie objc_msgSend
lub uzyskanie IMP
(wskaźnik funkcji implementacji metody) i wywołanie tego.
#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
działa poprzez uzyskanie IMP dla metody i wywołanie tego. IMP
dla kilku ostatnich wywoływanych metod są buforowane, więc jeśli wysyłasz komunikat Celu C w bardzo ciasnej pętli, możesz uzyskać akceptowalną wydajność. W niektórych przypadkach ręczne buforowanie IMP może dać nieco lepszą wydajność, chociaż jest to optymalizacja w ostateczności.