翻译自:the right way to swizzle in Object-C
在OC中,我们谈到swizzle
一般指用我们自己的方法代替原始的方法,调用原始的方法,执行的是我们自己的方法;我们使用runtime.h
提供的方法来实现;在运行时,OC的方法被转换为C结构体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
代表的是编码参数和返回值的类型;method_imp
代表的是函数指针指向了真正的函数地址;
你可以通过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])
}
然后我们调用如下方法
method_exchangeImplementations(m1, m2)
现在我们再看看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])
}
如果我们想执行原始代码我们需要调用[self swizzle_originalMethodName]
,对于_cmd
的值将是@selector(swizzle_originalMethodName)
;再如果原始代码中有使用过_cmd
,而在没有替换方法前,_cmd
的值是@selector(originalMethodName)
;这样的结果可能会导致未可知的错误;
- (void) originalMethodName //m1
{
assert([NSStringFromSelector(_cmd) isEqualToString:@“originalMethodNamed”]);
//this fails after swizzling //using
//method_exchangedImplementations()
//…
}
这里介绍一种方法,方法的核心是通过创建c函数来取代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
小结:我们可以通过一个C函数和method_setImplementation()
来实现我们的swizzle(当然我们也可以通过method_exchangeImplementations()
来实现,只是需要注意以上提到的_cmd
引发的问题);需要注意的是我们的C函数需要带有两个参数(id self
和 SEL _cmd
;这是由于OC的方法都会传递2个这两个隐藏的参数),另外需要将C函数转换为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.
网友评论