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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow