2016-08-17 5 views
1

Я использую метод swizzling и хотел бы вызвать исходную функцию после выполнения method_exchangeImplementations. У меня есть два проекта, для которых я настроен.Вызов функции из функции swizzled

Первый проект является основным проектом для приложения. Этот проект включает в себя всю логику для приложения. Обратите внимание, что при отображении представления вызывается originalMethodName.

@implementation ViewController 

- (void)originalMethodName 
{ 
    NSLog(@"REAL %s", __func__); 
} 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    NSLog(@"REAL %s", __func__); 

    [self originalMethodName]; 
} 

@end 

Второй проект включает только код для swizzling. У меня есть метод swizzle_originalMethodName, который включает в себя код, который я хочу ввести в основное приложение с помощью функции originalMethodName.

@implementation swizzle_ViewController 

- (void)swizzle_originalMethodName 
{ 
    NSLog(@"FAKE %s", __func__); 
} 

__attribute__((constructor)) static void initializer(void) 
{ 
    NSLog(@"FAKE %s", __func__); 

    Class c1 = objc_getClass("ViewController"); 
    Class c2 = [swizzle_ViewController class]; 
    Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName)); 
    Method m2 = class_getInstanceMethod(c2, @selector(swizzle_originalMethodName)); 
    method_exchangeImplementations(m1, m2); 
} 

@end 

надираться работает нормально (как показано в приведенном ниже примере), но теперь я хочу, чтобы иметь возможность вызывать originalMethodName из swizzle_originalMethodName

2016-08-17 14:18:51.765 testMacOS[7295:1297055] FAKE initializer 
2016-08-17 14:18:51.822 testMacOS[7295:1297055] REAL -[ViewController viewDidLoad] 
2016-08-17 14:18:51.822 testMacOS[7295:1297055] FAKE -[swizzle_ViewController swizzle_originalMethodName] 

Я пытался использовать NSInvocation, но я не повезло. Любые идеи, что я делаю неправильно?

Class c1 = objc_getClass("ViewController"); 
Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName)); 
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding( m1)]; 
NSInvocation *originalInvocation = [NSInvocation invocationWithMethodSignature:methodSignature]; 
[originalInvocation invoke]; 
+0

Как насчет просто вызова '[self swizzle_originalMethodName];'? Этот селектор должен вернуться к первоначальной реализации после обмена. –

+0

Это гений. Не знаю, почему это не произошло со мной. Я все еще получаю сообщение об ошибке, и я не уверен, как его решить. '- [ViewController swizzle_originalMethodName]: непризнанный селектор отправлен в экземпляр 0x7ff72974a670' –

+0

А, правильно ... вы перебираете разные классы. Мне нужно подумать об этом. –

ответ

1

Если вы изучаете иерархию классов, например. у вас есть подкласс, который swizzles один из своих методов предков с одним из своих, тогда вы просто имеете swizzled-in метод , очевидно, вызов сам - этот вызов фактически вызовет метод swizzled-out, поскольку методы были заменены. В вашем случае вы бы:

- (void)swizzle_originalMethodName 
{ 
    NSLog(@"FAKE %s", __func__); 
    [self swizzle_originalMethodName]; // call original 
} 

Это не работает в вашем случае, как вы кросс-класс swizzling, так self не ссылается на класс с swizzled-из метода. И у вас нет экземпляра класса swizzling, который вы можете назвать методом swizzled-out ...

Вот один из простых способов исправить это, что должен сделать ваш метод swizzle-in вызовите исходную реализацию, и вы можете это получить, когда вы настраиваете swizzling.

В Objective-C метод реализуется функцией, чьи первые два аргумента являются ссылкой на объект, на который вызывается метод, а селектор и остальные аргументы являются аргументами метода. Например NSString метод:

- (NSRange)rangeOfString:(NSString *)aString 

реализуется с помощью функции что-то вроде:

NSRange rangeOfStringImp(NSString *self, SEL cmd, NSString *aString) 

Вы можете получить функцию указатель на эту функцию реализации с использованием method_getImplementation.

Для кода, первый в ваш swizzle_ViewController объявить тип для функции реализации методы вы swizzling, и глобальный для хранения указателя функции:

typedef void (*OriginalImpType)(id self, SEL selector); 
static OriginalImpType originalImp; 

Теперь в вашем методе initializer вам нужно сохранить реализацию методы, вы можете сделать это, добавив строку, показанную:

Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName)); 
originalImp = (OriginalImpType)method_getImplementation(m1); // save the IMP of originalMethodName 

Наконец ваш swizzled в вызове методы сохраненных реализаций:

- (void)swizzle_originalMethodName 
{ 
    NSLog(@"FAKE %s", __func__); 
    originalImp(self, @selector(originalMethodName)); // call the original IMP with the correct self & selector 
} 

Дополнительно: Вышеприведенные работает правильно, однако это немного больше, чем требуется - реализации методов являются обмен и один хранится в глобальной переменной, все, что вам действительно нужно сделать, это сохранить первоначальную реализацию m1 а затем установите его реализацию в значение m2. Вы можете решить эту проблему, заменив вызов method_exchangeImplementations с:

method_setImplementation(m1, method_getImplementation(m2)); 

Это немного больше печатать, но несколько яснее, как к тому, что на самом деле должно быть сделано.

HTH

+0

Ты потрясающий! Это работает! Вчера я бил головой о стену, пытаясь заставить это работать. спасибо –