Method Swizzling已经被聊烂了,都知道这是Objective-C的黑魔法,能够交换两个方法的实现。今天我也来聊一下Method Swizzling。objective-c
咱们先贴上这一大把代码吧ui
@interface UIViewController (Swizzling) @end @implementation UIViewController (Swizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(swizzling_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } #pragma mark - Method Swizzling - (void)swizzling_viewWillAppear:(BOOL)animated { [self swizzling_viewWillAppear:animated]; NSLog(@"==%@",NSStringFromClass([self class])); } @end
好的,上面就是Method Swizzling的使用方法,将方法- (void)swizzling_viewWillAppear:(BOOL)animated
和系统级方法- (void)viewWillAppear:(BOOL)animated
交换。经常使用的场景就是埋点,这个咱就不细说了。spa
这里咱们说一个点就是实现代码的位置问题。code
咱们的交换代码必须只能调用一次,若是执行屡次的,那不就把交换的实现又换回来了吗,因此咱们必须找一个只会调用一次的地方来写实现交换的代码。blog
咱们都知道+load
会在加载类的时候执行,并且只执行一次,可是为了进一步保障他只能执行一次,咱们使用了dispatch_once(由于会有人手动调用+load方法)。继承
此外+load
方法还有一个很是重要的特性,那就是子类、父类和分类中的+load
方法的实现是被区别对待的。换句话说在 Objective-C runtime 自动调用+load
方法时,分类中的+load
方法并不会对主类中的+load
方法形成覆盖。get
在取到了SEL和Method以后,执行了下面这句代码源码
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
而后根据success来作不一样的处理。io
这里咱们先说结论,就拿咱们上面的代码做为例子。table
若是咱们的主类,也就是UIViewController(咱们是给UIViewController建立的Category)实现了viewWillAppear:
方法,success为NO,若是没有实现,则为YES。在咱们的例子中的UIViewController确定是实现了viewWillAppear:
方法的,因此success确定为NO。
若是咱们这里给一个自定义的VC建立Category实现Swizzling,而且VC没有显式的实现viewWillAppear:
(继承父类的),这时success就是YES了。
你们能够本身建立不一样类和类别的试一试。
咱们经过runtime的源码来看一下class_addMethod
内部作了什么操做。
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { if (!cls) return NO; mutex_locker_t lock(runtimeLock); return ! addMethod(cls, name, imp, types ?: "", NO); }
class_addMethod
返回值是对addMethod
返回值取反。这个地方稍微有点别扭。咱们能够看一下addMethod
方法,返回值是IMP,因此说:
主类实现了被Swizzling的方法,success为NO,即class_addMethod返回NO,addMethod返回值不为空。
主类未实现了被Swizzling的方法,success为YES,即class_addMethod返回YES,addMethod返回值为空。
// addMethod方法的主要代码 method_t *m; if ((m = getMethodNoSuper_nolock(cls, name))) { // already exists if (!replace) {//replace==NO (class_addMethod) result = m->imp; } else {//(class_replaceMethod) result = _method_setImplementation(cls, m, imp); } } else { // fixme optimize method_list_t *newlist; newlist = (method_list_t *)calloc(sizeof(*newlist), 1); newlist->entsizeAndFlags = (uint32_t)sizeof(method_t) | fixed_up_method_list; newlist->count = 1; newlist->first.name = name; newlist->first.types = strdupIfMutable(types); newlist->first.imp = imp; prepareMethodLists(cls, &newlist, 1, NO, NO); cls->data()->methods.attachLists(&newlist, 1); flushCaches(cls); result = nil; }
咱们结合源码来梳理上面提到的两种状况。
首先method_t其实就是一个储存方法的细节的结构体。
经过m = getMethodNoSuper_nolock(cls, name)
查找类中对应的方法的信息,包括方法名,实现等等。
调用m = getMethodNoSuper_nolock(cls, name)
查找,这里应该是能够找到的,class_addMethod
方法中调用addMethod
的时候relpace传的是NO,因此会执行result = m->imp;
,其实就是给result赋了个值,让他不为空。class_addMethod
的返回值是addMethod
返回值取反,因此此时class_addMethod
返回为NO。
继续往下执行method_exchangeImplementations(originalMethod, swizzledMethod);
,这就很好理解了,就是直接把须要交换的两个方法的实现直接交换。
getMethodNoSuper_nolock
找不到,m仍是为空,因此会执行else下面的代码,这里面的代码其实很明显,就是动态的为咱们的主类建立实现了须要被swizzling的方法,固然了,由于此时咱们传入class_addMethod
方法的sel是须要被swizzling的方法,可是实现已是传了须要替换后的实现,因此执行完else里面的代码以后,咱们的须要被swizzling的方法的实现,已经指向了替换后的实现,也就是viewWillAppear:
的IMP其实此时已经指向了swizzling_viewWillAppear:
的IMP。
最后result置为nil,因此class_addMethod
返回值就是YES,success为YES。
最后再执行class_replaceMethod
方法。
从源码中来看,class_replaceMethod
和class_addMethod
都是调用了addMethod
,但有两点不一样,一来是class_replaceMethod
在调用addMethod
时,参数replace传YES,再者就是由于class_replaceMethod
的返回值是一个IMP,因此和addMethod
是一致的,直接return了addMethod
的返回值。
在经过class_replaceMethod
调用addMethod
的时候,虽然咱们主类以前没实现要被swizzling的方法,可是在上一步中,咱们已经动态的添加了,因此此时getMethodNoSuper_nolock
是能找到的。
最终执行result = _method_setImplementation(cls, m, imp);
。_method_setImplementation
内部实现很简单,先用一个old记录下m->imp 而后再把m->imp设置为传入的imp,随后返回old,其实也就是返回了没交换前的m->imp。
这样咱们就经过_method_setImplementation
方法把咱们的swizzling_viewWillAppear:
的IMP指向了viewWillAppear:
的IMP。完成了viewWillAppear:
和swizzling_viewWillAppear:
两个方法实现的交换。