Method Swizzling: 即方法交换。
先来学点Objective-C的运行时来热热身。
一、在Objective-C中,message与方法的真正实现是在执行阶段绑定的,而非编译阶段。编译器会将消息发送转换成对objc_msgSend方法的调用。
objc_msgSend方法含两个必要参数:receiver、方法名(即:selector),如:
[receiver message]; 将被转换为:objc_msgSend(receiver, selector);
objc_msgSend方法也能hold住message的参数,如:
objc_msgSend(receiver, selector, arg1, arg2, …);
objc_msgSend方法会做按照顺序进行以下操作,以完成动态绑定:
- 查找selector所指代的程序(方法的真正实现即方法的名字)。因为不同类对同一方法有不同的实现,所以对方法的真正实现的查找依赖于receiver的类
- 调用该实现,并将一系列参数传递过去
- 将该实现的返回值作为自己的返回值,返回之
二、Object-c 运行时允许你修改selector(method name)到implementation(the method code itself)的映射。
在运行时,OC 的方法被称为一种叫 Method 的结构体,这种 objc_method 类型的结构体定义为
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;//方法的 selector,可以理解为运行时的方法名;
char *method_types OBJC2_UNAVAILABLE;//是一个参数和返回值类型编码的字符串
IMP method_imp OBJC2_UNAVAILABLE;//是指向方法实现的指针
}
当我们向一个receiver的一个方法selector发送消息时,而查找消息的唯一依据就是selector的名字,每个selector都有它自己影射的实现(这个实现我们可以通过class_getInstanceMethod或者class_getClassMethod方法获取,通过这两个方法获取回来的影射实现即为结构体Method);然后我们再通过 method_getImplementation获取IMP,selector和IMP这两部分是分开的,于是我们可以在程序运行的时候动态的改变方法名和方法实现之间的映射关系(即改变Method中的IMP),从而实现改变代码的实现过程。
三、具体代码:
@implementation NSArray (swizzle)
-(id)xxx_lastObject
{
id ret = [self xxx_lastObject];//被交换过后的xxx_lastObject实际上已经指向了lastObject,所以不存在递归问题
NSLog(@"ret======:%@",ret);
return ret;
}
Swizzling应该在+load方法中实现。
每个类的这两个方法会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,只有实现了才会被执行。
+(void)load
{
//获取lastObject方法的Method结构体
Method originalMdthod = class_getInstanceMethod([self class], @selector(lastObject));
//获取lastObject方法的Method结构体的IMP,仅用于观察
IMP originalIMP = method_getImplementation(originalMdthod);
//获取xxx_lastObject方法的Method结构体
Method myMethod = class_getInstanceMethod([self class], @selector(xxx_lastObject));
//获取xxx_lastObject方法的Method结构体的IMP,仅用于观察
IMP myIMP = method_getImplementation(myMethod);
//交换两个结构体originalMdthod和myMethod的IMP指针
method_exchangeImplementations(originalMdthod, myMethod);
//获取交换IMP指针后originalMdthod方法的Method结构体的IMP,仅用于观察
IMP originalIMPAfterSwizzle = method_getImplementation(originalMdthod);
//获取交换IMP指针后xxx_lastObject方法的Method结构体的IMP,仅用于观察
IMP myIMPAfterSwizzle = method_getImplementation(myMethod);
}
这是debug模式下在控制台下的打印:
Printing description of originalIMP:
(IMP) originalIMP = 0x2c5622cd (CoreFoundation`-[NSArray lastObject] + 1)
Printing description of originalIMPAfterSwizzle:
(IMP) myIMP = 0x000191e1 (swizzle`-[NSArray(swizzle) xxx_lastObject] + 1 at NSArray+swizzle.m)
Printing description of myIMPAfterSwizzle:
(IMP) originalIMPAfterSwizzle = 0x000191e1 (swizzle`-[NSArray(swizzle) xxx_lastObject] + 1 at NSArray+swizzle.m)
Printing description of myIMP:
(IMP) myIMPAfterSwizzle = 0x2c5622cd (CoreFoundation`-[NSArray lastObject] + 1)
由打印出来的结结果可以看到,交换后的originalIMPAfterSwizzle的IMP指向了myIMP,而myIMPAfterSwizzle指向了originalIMP
使用一下这个东西:
NSArray *arr = @[@"0",@"1",@"2",@"3",@"4"];
NSString *str = [arr lastObject];//被交换过后的lastObject实际上已经指向了xxx_lastObject
NSLog(@"test result:%@",str);
控制台下面的打印是这样的:
2014-10-08 12:25:25.314 swizzle[414:27576] ret======:4
2014-10-08 12:25:25.315 swizzle[414:27576] test result:4
有了Method Swizzling这个东西之后,你就可以"修补"那些没有源码的方法,如(AppKit,FoundationKit,或第三方的库等)。
和category不同的是,category如果定义了一个原来相同的方法,那么会直接覆盖原来的方法。Method Swizzling让你可以在替代原来的方法的同时可以调用原来的方法。有点像subclassing。