3

Мне нужно заменить некоторые методы реализации конкретных классов Objective-C. Набор функций из библиотеки objc/runtime способен это сделать. Чтобы упростить этот вопрос, я просто написать простейший пример кода следующим образом:Почему ARC вызывает EXC_BAD_ACCESS при выполнении swizzling-функций с использованием class_replaceMethod из библиотеки времени выполнения objc?

#import <Foundation/Foundation.h> 
#import <objc/runtime.h> 

@interface MyClass : NSObject 
- (void) func; 
@end 

@implementation MyClass 

- (void) func { 
    NSLog(@"Hello from MyClass!"); 
} 

@end 

//Original implementation of the method 
static IMP gOriginalFunc = nil; 

//Hook function that will take place of the original method 
void HookFunc(id self, SEL _cmd) 
{ 
    NSLog(@"[MyClass func] is hooked!"); 
    gOriginalFunc(self, _cmd);//EXC_BAD_ACCESS occurs here when ARC is enabled!! 
} 

int main(int argc, char *argv[]) 
{ 
    Class clsMyClass = objc_getClass("MyClass"); 
    // Restore the original method implementation: 
    gOriginalFunc = class_getMethodImplementation(clsMyClass, @selector(func)); 
    Method mtdFunc = class_getInstanceMethod(clsMyClass, @selector(func)); 
    // Replace implementaion of the method of my own: 
    class_replaceMethod(clsMyClass, @selector(func), IMP(HookFunc), method_getTypeEncoding(mtdFunc)); 
    objc_registerClassPair(clsMyClass); 

    MyClass* obj = [[MyClass alloc] init]; 
    [obj func]; 

    return 0; 
} 

Я просто заменить оригинальную реализацию [MyClass FUNC]. Когда флаг компилятора -fno-ObjC-дуга устанавливается, этот код работает отлично:

2014-12-18 11:59:17.524 Hooker[749:72783] [MyClass func] is hooked! 
2014-12-18 11:59:20.361 Hooker[749:72783] Hello from MyClass! 

Но проблема возникает, когда ARC включена (установив флаг компилятора, как -fobjc-дуги). Сигнал EXC_BAD_ACCESS вызывается при вызове gOriginalFunc (как говорят комментарии). Я не знаю причины, может ли кто-нибудь сказать?

+0

Примечание: «крюк» = "swizzling" in objc parlance – justin

ответ

1

Ваш прототип функции неточный, и компилятор неправильно настроил вызов. С ARC, который включает операции подсчета ссылок. Либо это неопределенное поведение.

Чтобы исправить программу, информирует компилятор о фактической сигнатуры функции, так что не завинчивать вызов:

typedef void (*HookFunc_Signature)(id, SEL); // << != IMP's signature 
static HookFunc_Signature gOriginalFunc = nil; 

... позже:

gOriginalFunc = (HookFunc_Signature)class_getMethodImplementation(clsMyClass, @selector(func)); 
+0

Большое спасибо! Это помогает много. Хотя я не совсем понимаю, почему вопрос возврата. Я обнаружил, что эта проблема возникает в проекте с целевой версией SDK раньше 7.0, потому что IMP был определен как: typedef id (* IMP) (id, SEL). Он отличается только в обратном типе от вашей HookFunc_Signature. – godspeed1024

+0

@ user1224028 зависит от ABI и ARC (которые могут вводить дисперсии objc). компилятор должен знать, как настроить вызов функции, иначе он будет читать и/или записывать память, которую он не должен, и, возможно, интерпретировать его по-разному. ARC добавляет сложности, потому что вызов также может привести к вызовам сохранения/выпуска, введенным компилятором. в качестве примера, компилятор может сообщать исполняемой среде выполнить операцию отсчета ссылок, где сохраняется возвращаемое значение (например, значение регистра). с функцией void, которая будет значением мусора (= неопределенное поведение для обработки как объекта). – justin

+0

Предлагаю вам прочитать https://blog.newrelic.com/2014/04/16/right-way-to-swizzle/ (и особенно вторую сноску). Он волшебным образом установил этот EXC_BAD_ACCESS для меня, и я мог понять, почему! В принципе, компилятор (и ARC) имеет тенденцию сохранять результат swizzled методов, что приводит к сбою, когда эти методы не возвращают экземпляр NSObject. Правильное выполнение результата этих функций не позволяет компилятору слишком много удерживать – Daladim