一.Method Swizzling介绍
objective-c 方法分为方法名(selector)和方法实现(IMP)两部分,当调用方法时,Objective-C运行时根据selecotr去找匹配的实现代码。见”关于Objective-C方法的IMP“的介绍。MethodSwizzling是一种在运行时修改方法名与方法实现映射关系的技术,常用于在系统内部方法的基础上增加新的处理流程。
假设我们有一个类T,其有两个方法为swizzleMethod方法和altMethod方法。
- (void)swizzleMethod {
NSLog(@"Original method called");
}
- (void)altMethod {
NSLog(@"Alternative method called");
[self altMethod];
}
我们以如下方式使用
T *t = [[T alloc] init];
[t swizzleMethod];
NSLog(@"Methods swizzled");
MethodSwizzle([T class],
@selector(swizzleMethod),
@selector(altMethod));
[t swizzleMethod];
[t release];
则输出为:
Original method called
Methods swizzled
Alternative method called
Original method called
第一次调用swizzleMethod方法时,显示是Original method called,与函数实现相同;当进行了MethodSwizzle之后,再次调用swizzleMethod方法,则多输出了一行Alternative method called,实际调用了altMethod,且altMethod内部仍然调用了altMethod方法,看似会死循环的代码。
解释如下:方法实现分为selector和IMP两部分,则我们分别用selector(swizzleMethod)和IMP(swizzleMethod)表示。
未使用MethodSwizzle之前
selector(swizzleMethod) -> IMP(swizzleMethod)
selector(altMethod) -> IMP(altMethod)
这时候如代码所示,如果[t swizzleMethod]会输出Original method called;而[t altMethod]则会死循环。
当我们进行了MethodSwizzle方法是,实际上交换了swizzleMethod和altMethod的IMP部分,即
selector(swizzleMethod) -> IMP(altMethod)
selector(altMethod) -> IMP(swizzleMethod)
这时候我们再次调用[t swizzleMethod],实际执行却是altMethod的IMP,所以会先输出Alternative method called,而在IMP(altMethod)方法中调用[t altMethod],则实际上执行又是IMP(swizzleMethod),所以会再次输出Original method called。用这种方式在原来swizzleMethod方法上增加了输出Alternative method called的处理,而不用修改函数调用。
二.经典MethodSwizzle实现
MethodSwizzle函数的实现如下,即交换两个方法的IMP。
import
void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel) {
Method orig_method = nil, alt_method = nil;
// First, look for the methods
orig_method = class_getInstanceMethod(aClass, orig_sel);
alt_method = class_getInstanceMethod(aClass, alt_sel);
// If both are found, swizzle them
if ((orig_method != nil) && (alt_method != nil))
{
char temp1;
IMP temp2;
temp1 = orig_method->method_types;
orig_method->method_types = alt_method->method_types;
alt_method->method_types = temp1;
temp2 = orig_method->method_imp;
orig_method->method_imp = alt_method->method_imp;
alt_method->method_imp = temp2;
}
}
这种实现有一个问题,如果需要修改的方法是在父类中实现的,那么MethodSwizzle会把所有父类的方法改变了,而我们仅仅想修改当前子类的实现。比如,我们仅想修改UIButton的行为,但修改的方法是继承自UIView的,进行了MethodSwizzle之后,实际上将UIView及所有UIView的子类方法都修改了,这不是我们想要的。
三.解决继承问题的MethodSwizzle
解决继承问题的方法之一是,如果修改的或用于替换的方法来自父类,则拷贝一份放到当前类中,然后再进行MethodSwizzle。
见Method Swizzling reimplemented,核心代码如下:
static BOOL _PerformSwizzle(Class klass, SEL origSel, SEL altSel, BOOL forInstance);
BOOL ClassMethodSwizzle(Class klass, SEL origSel, SEL altSel) {
return _PerformSwizzle(klass, origSel, altSel, NO);
}
BOOL MethodSwizzle(Class klass, SEL origSel, SEL altSel) {
return _PerformSwizzle(klass, origSel, altSel, YES);
}
// if the origSel isn't present in the class, pull it up from where it exists
// then do the swizzle
BOOL _PerformSwizzle(Class klass, SEL origSel, SEL altSel, BOOL forInstance) {
// First, make sure the class isn't nil
if (klass != nil) {
Method origMethod = NULL, altMethod = NULL;
// Next, look for the methods
Class iterKlass = (forInstance ? klass : klass->isa);
void *iterator = NULL;
struct objc_method_list *mlist = class_nextMethodList(iterKlass, &iterator);
while (mlist != NULL) {
int i;
for (i = 0; i < mlist->method_count; ++i) {
if (mlist->method_list[i].method_name == origSel) {
origMethod = &mlist->method_list[i];
break;
}
if (mlist->method_list[i].method_name == altSel) {
altMethod = &mlist->method_list[i];
break;
}
}
mlist = class_nextMethodList(iterKlass, &iterator);
}
if (origMethod == NULL || altMethod == NULL) {
// one or both methods are not in the immediate class
// try searching the entire hierarchy
// remember, iterKlass is the class we care about - klass || klass->isa
// class_getInstanceMethod on a metaclass is the same as class_getClassMethod on the real class
BOOL pullOrig = NO, pullAlt = NO;
if (origMethod == NULL) {
origMethod = class_getInstanceMethod(iterKlass, origSel);
pullOrig = YES;
}
if (altMethod == NULL) {
altMethod = class_getInstanceMethod(iterKlass, altSel);
pullAlt = YES;
}
// die now if one of the methods doesn't exist anywhere in the hierarchy
// this way we won't make any changes to the class if we can't finish
if (origMethod == NULL || altMethod == NULL) {
return NO;
}
// we can safely assume one of the two methods, at least, will be pulled
// pull them up
size_t listSize = sizeof(struct objc_method_list);
if (pullOrig && pullAlt) listSize += sizeof(struct objc_method); // need 2 methods
struct objc_method_list *mlist = malloc(listSize);
mlist->obsolete = NULL;
int i = 0;
if (pullOrig) {
memcpy(&mlist->method_list[i], origMethod, sizeof(struct objc_method));
origMethod = &mlist->method_list[i];
i++;
}
if (pullAlt) {
memcpy(&mlist->method_list[i], altMethod, sizeof(struct objc_method));
altMethod = &mlist->method_list[i];
i++;
}
mlist->method_count = i;
class_addMethods(iterKlass, mlist);
}
// now swizzle
IMP temp = origMethod->method_imp;
origMethod->method_imp = altMethod->method_imp;
altMethod->method_imp = temp;
return YES;
}
return NO;
}
MethodSwizzle.h
MethodSwizzle.m
四.method_exchangeImplementations API
Apple在10.5引入了新的API method_exchangeImplementations用于交换方法的IMP,其同样没有解决继承问题,使用方法如下:
Method myReplacementMethod =
class_getClassMethod([MyClass class], @selector(myReplacementMethod));
Method windowDealloc =
class_getInstanceMethod([NSWindow class], @selector(dealloc));
method_exchangeImplementations(myReplacementMethod, windowDealloc);
五.JRSwizzle
jrswizzle中总结了以上几种Method Swizzling的实现,并实现了一个完善的JRSwizzle版本,可以解决继承问题,且可以在不同版本系统下使用。JRSwizzle在NSObjective的类别中实现,使用方法为:
[SomeClass jr_swizzle:@selector(foo) withMethod:@selector(my_foo) error:&error];
参考:
MethodSwizzling wiki
Method Swizzling reimplemented
jrswizzle
Supersequent implementation
Objective-C Runtime Reference