翻譯自:the right way to swizzle in Object-C
在OC中铃慷,我們談到swizzle
一般指用我們自己的方法代替原始的方法信柿,調(diào)用原始的方法晃琳,執(zhí)行的是我們自己的方法闲延;我們使用runtime.h
提供的方法來實現(xiàn)熟空;在運行時,OC的方法被轉(zhuǎn)換為C結(jié)構(gòu)體Method
竟纳;
//a typedef of struct objc_method defined as:
struct objc_method
{
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
method_name
代表的是方法的selector撵溃;method_types
代表的是編碼參數(shù)和返回值的類型疚鲤;method_imp
代表的是函數(shù)指針指向了真正的函數(shù)地址锥累;
你可以通過runtime
的以下方法拿到Method
Method class_getClassMethod(Class aClass, SEL aSelector);
Method class_getInstanceMethod(Class aClass, SEL aSelector);
我們可以通過IMP
的method_setImplementation(Method method, IMP imp)
方法改變Method
中的imp
;這里我們稍后再說集歇,我們先來看看另外一個方法
void method_exchangeImplementations(Method m1, Method m2)
為了能更好的理解這個方法桶略,我們先來看看Method m1
和Method m2
Method m1 { //this is the original method. we want to switch this one with
//our replacement method
SEL method_name = @selector(originalMethodName)
char *method_types = “v@:“ //returns void, params id(self),selector(_cmd)
IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
}
Method m2 { //this is the swizzle method. We want this method executed when [MyClass
//originalMethodName] is called
SEL method_name = @selector(swizzle_originalMethodName)
char *method_types = “v@:”
IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
}
然后我們調(diào)用如下方法
method_exchangeImplementations(m1, m2)
現(xiàn)在我們再看看m1
和m2
Method m1 { //this is the original Method struct. we want to switch this one with
//our replacement method
SEL method_name = @selector(originalMethodName)
char *method_types = “v@:“ //returns void, params id(self),selector(_cmd)
IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
}
Method m2 { //this is the swizzle Method struct. We want this method executed when [MyClass
//originalMethodName] is called
SEL method_name = @selector(swizzle_originalMethodName)
char *method_types = “v@:”
IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
}
如果我們想執(zhí)行原始代碼我們需要調(diào)用[self swizzle_originalMethodName]
,對于_cmd
的值將是@selector(swizzle_originalMethodName)
;再如果原始代碼中有使用過_cmd
际歼,而在沒有替換方法前惶翻,_cmd
的值是@selector(originalMethodName)
;這樣的結(jié)果可能會導(dǎo)致未可知的錯誤鹅心;
- (void) originalMethodName //m1
{
assert([NSStringFromSelector(_cmd) isEqualToString:@“originalMethodNamed”]);
//this fails after swizzling //using
//method_exchangedImplementations()
//…
}
這里介紹一種方法吕粗,方法的核心是通過創(chuàng)建c函數(shù)來取代OC的方法;
eg:
void __Swizzle_OriginalMethodName(id self, SEL _cmd)
{
//code
}
IMP swizzleImp = (IMP)__Swizzle_OriginalMethodName;
method_setImplementation(method, swizzleImp);
IMP originalImp = method_setImplementation(method,swizzleImp);
接下來列舉一個具體的例子旭愧;
@interface SwizzleExampleClass : NSObject
- (void) swizzleExample;
- (int) originalMethod;
@end
static IMP __original_Method_Imp;
int _replacement_Method(id self, SEL _cmd)
{
assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
//code
int returnValue = ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd);
return returnValue + 1;
}
@implementation SwizzleExampleClass
- (void) swizzleExample //call me to swizzle
{
Method m = class_getInstanceMethod([self class], @selector(originalMethod));
__original_Method_Imp = method_setImplementation(m, (IMP)_replacement_Method);
}
- (int) originalMethod
{
//code
assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
return 1;
}
@end
然后我們可以驗證:
SwizzleExampleClass* example = [[SwizzleExampleClass alloc] init];
int originalReturn = [example originalMethod];
[example swizzleExample];
int swizzledReturn = [example originalMethod];
assert(originalReturn == 1); //true
assert(swizzledReturn == 2); //true
小結(jié):我們可以通過一個C函數(shù)和method_setImplementation()
來實現(xiàn)我們的swizzle(當然我們也可以通過method_exchangeImplementations()
來實現(xiàn)颅筋,只是需要注意以上提到的_cmd
引發(fā)的問題);需要注意的是我們的C函數(shù)需要帶有兩個參數(shù)(id self
和 SEL _cmd
输枯;這是由于OC的方法都會傳遞2個這兩個隱藏的參數(shù))议泵,另外需要將C函數(shù)轉(zhuǎn)換為IMP
;最后一點不知道是什么意思桃熄;
//you may have to case the IMP call if it returns a void.
//This is because ARC assumes all IMPs return an id and
//will try to retain void and primitive types.
IMP anImp; //represents objective-c function
// -UIViewController viewDidLoad;
((void(*)(id,SEL))anImp)(self,_cmd); //call with a cast to prevent
// ARC from retaining void.