Objective-C之Method Swizzle
一、原理
我们平时编写的OC代码,底层都是基于Runtime来实现的。Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一。
1). 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法 Method Swizzle。
2). Method Swizzle 指的是改变一个已存在的选择器对应的实现的过程。OC中方法的调用能够在运行时通过改变,通过改变类的调度表中选择器到最终函数间的映射关系。
3). 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector对应的方法实现。
4). 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的方法实现。
5). 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP。
6). 我们可以利用 class_replaceMethod 来修改类。
7). 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。
8). 归根结底,都是偷换了selector的IMP。。
二、实现
import <objc/runtime.h>
@implementation UIViewController (Tracking)
-
(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(clq_viewWillAppear:);Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // 如果是替换类方法,使用下面 // Class class = object_getClass((id)self); // ... // Method originalMethod = class_getClassMethod(class, orginalSelector); // Method swizzledMethod = class_getClassMethod(class, swizzledSelctor); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(orgiginalMethod, swizzledMethod); }
});
}
pragma mark - Method Swizzling
- (void)clq_viewWillAppear:(BOOL)animated
{
[self clq_viewWillAppear:animated];
}
三、注意点
1、object_getclass((id)self)和[self class]的区别
1)runtime经典图解分析
Paste_Image.png
2)源码
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
3)[self class] 指向类对象,类对象存储成员函数,而object_getclass((id)self)指向meta类,meta类指向类方法
2、clq_viewWillAppear实现是否造成递归
答案是否的,clq_viewWillAppear方法里面调用clq_viewWillApear方法的时候,由于clq_viewWillApear和viewWillApear的实现已交换了,这时候调用clq_viewWillApear的时候,实际上是调用swizzle之前的方法,viewWillApear,所以不会造成递归调用的
3、不用class_addMethod进行判断,简单用method_exchangeImplementations有什么问题
由于当要重写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。这时候用method_exchangeImplementations是达不到目的的,需要在目标类增加一个新的实现方法(override)