2012-02-29 11 views
17

Mam kategorię na NSObject, która ma na celu pewne rzeczy. Kiedy wywołuję to na obiekcie, chciałbym zastąpić jego metodę dealloc, aby wykonać pewne czyszczenie.Swizzling pojedynczej instancji, a nie klasy

Chciałem to zrobić za pomocą metody swizzling, ale nie mogłem dowiedzieć się, jak. Jedynymi przykładami, które znalazłem, sĘ ... to, jak zastĘ ... pić implementację metody dla całej klasy (w moim przypadku byłoby to nadpisanie dealloc dla ALL NSObjects - czego nie chcę).

Chcę zastąpić metodę dealloc konkretnych instancji obiektu NSObject.

@interface NSObject(MyCategory) 
-(void)test; 
@end 

@implementation NSObject(MyCategory) 
-(void)newDealloc 
{ 
    // do some cleanup here 
    [self dealloc]; // call actual dealloc method 
} 
-(void)test 
{ 
    IMP orig=[self methodForSelector:@selector(dealloc)]; 
    IMP repl=[self methodForSelector:@selector(newDealloc)]; 
    if (...) // 'test' might be called several times, this replacement should happen only on the first call 
    { 
    method_exchangeImplementations(..., ...); 
    } 
} 
@end 

Odpowiedz

18

Tak naprawdę nie można tego zrobić, ponieważ obiekty nie mają własnych tabel metod. Tylko klasy mają tabele metod i jeśli je zmienisz, wpłyną na każdy obiekt tej klasy. Istnieje jednak prosty sposób: Zmienić klasę obiektu w czasie wykonywania na dynamicznie utworzoną podklasę. Ta technika, zwana również isa-swizzling, jest używana przez firmę Apple do implementacji automatycznego KVO.

Jest to potężna metoda i ma swoje zastosowania. Ale dla twojego przypadku jest łatwiejsza metoda z użyciem powiązanych obiektów. Zasadniczo używasz objc_setAssociatedObject, aby powiązać inny obiekt z pierwszym obiektem, który oczyszcza go w swoim dealloc. Więcej informacji można znaleźć w this blog post on Cocoa is my Girlfriend.

+0

Dzięki za odpowiedź. Zasadniczo dla każdego połączenia przydzielam nowy obiekt (który implementuję) i ustawię go jako powiązany obiekt, a ja zajmuję się czyszczeniem wewnątrz jego dealloc? –

+0

Dokładnie. Spójrz na ten wpis na blogu, ma nawet fajną kategorię na 'NSObject', która pozwala ci rejestrować bloki, które będą wywoływane podczas dealloc dowolnego obiektu. – Sven

8

wybór Metoda opiera się na klasiew instancji obiektu, więc metoda swizzling dotyczy wszystkich wystąpień tej samej klasy - jak odkrył.

Ale możesz zmienić klasę instancji, ale musisz być ostrożny! Oto zarys, że masz klasę:

@instance MyPlainObject : NSObject 

- (void) doSomething; 

@end 

Teraz, jeśli dla tylko niektóre z wystąpień MyPlainObject chcesz zmienić zachowanie doSomething najpierw zdefiniować podklasę:

@instance MyFancyObject: MyPlainObject 

- (void) doSomething; 

@end 

Teraz możesz wyraźnie wykonywać instancje MyFancyObject, ale musimy wykonać instancję istniejącą z MyPlainObject i przekształcić ją w MyFancyObject, aby uzyskać nowe zachowanie. Do tego możemy swizzle klasę, dodać następujące MyFancyObject:

static Class myPlainObjectClass; 
static Class myFancyObjectClass; 

+ (void)initialize 
{ 
    myPlainObjectClass = objc_getClass("MyPlainObject"); 
    myFancyObjectClass = objc_getClass("MyFancyObject"); 
} 

+ (void)changeKind:(MyPlainObject *)control fancy:(BOOL)fancy 
{ 
    object_setClass(control, fancy ? myFancyObjectClass : myPlainObjectClass); 
} 

teraz za każdym oryginalny Instancji MyPlainClass można przełączyć się zachowywać jako MyFancyClass i vice-versa:

MyPlainClass *mpc = [MyPlainClass new]; 

... 

// masquerade as MyFancyClass 
[MyFancyClass changeKind:mpc fancy:YES] 

... // mpc behaves as a MyFancyClass 

// revert to true nature 
[MyFancyClass changeKind:mpc: fancy:NO]; 

(Niektóre) z zastrzeżeń:

Możesz wykonać tylko tylko, jeśli podklasa zastępuje lub dodaje metody, i dodaje static (klasa) zmienne.

Potrzebujesz również pod-klasy na każdą klasę, którą chcesz zmienić zachowanie, nie możesz mieć jednej klasy, która może zmienić zachowanie wielu różnych klas.

+0

Ale w takim przypadku muszę wiedzieć z góry, jaka byłaby klasa tej instancji. W moim przypadku obiektem może być wszystko (nawet obiekt stworzony przez sam SDK), więc nie mogę założyć, że znam ten typ. –

+0

Tak, to jedna z zastrzeżeń! Jeśli jest to potrzebne, przejdź do powiązanej trasy obiektu. Ale jeśli celujesz w określone klasy, swizzling jest nieco (subiektywnie rzecz jasna) prostszy niż powiązane obiekty i bardziej ogólny, ponieważ możesz łatwo przesłonić dowolną metodę. – CRD

+0

Mimo że korzystałem z powiązanego obiektu jako mojego rozwiązania, doceniam twoją odpowiedź. Mam pytanie na ten temat: ponieważ przydaje się tylko nadpisywanie/dodawanie metod - jaka jest różnica między tym a kategorią? –

1

Zrobiłem SWIZzling API, który również zawiera swizzling specyficzne dla instancji.Myślę, że dokładnie to, czego szukasz: https://github.com/JonasGessner/JGMethodSwizzler

Działa, tworząc dynamiczną podklasę dla konkretnej instancji, która swizzling w środowisku wykonawczym.

Powiązane problemy