OC 黑魔法之 Method Swizzling

基础实现

Method swizzling(有些人也叫它方法交换)对绝大多数OC开发者来说都是很熟悉但并没有太多应用场景的一个东西。但是了解它对于了解OC底层runtime有很好的帮助。而且可以绕过一个限制,实现某些特殊的功能。
使用Method swizzling,写法是基本相同的。比如说要给UIApplicationsendEvent:方法做交换,创建一个UIApplication的分类,在load方法中实现。

Method sendEventMethod = class_getInstanceMethod([self class], @selector(sendEvent:));
    Method sendNotifyEventMethod = class_getInstanceMethod([self class], @selector(my_sendEvent:));
    
    method_exchangeImplementations(sendEventMethod, sendNotifyEventMethod);

然后再写一个你想用来替换系统sendEvent:实现的方法,在这个例子中叫做my_sendEvent:,然后再这个实现中调用自身。

- (void)my_sendEvent:(UIEvent *)event {
    
    NSLog(@"send evnet before %@",event);
    
    [self my_sendEvent:event];
    
    NSLog(@"send event after %@",event);
}

按照日常写代码的经验,在方法中调用自己,这不写成死循环了吗。但在这里,却并不会形成循环。这是因为我们在上面调用了method_exchangeImplementations方法,将方法名和方法的实现进行了交换,当系统调用sendEvent:方法时,调用到了my_sendEvent:的实现中,这时候我就可以做我想要的处理,并在这个方法中调用my_sendEvent:方法,但其实调用的实现是系统的sendEvent:,这样可以在完成系统实现的基础上完成了自己想要的逻辑。

  • 在这个方法中打断点也可以看到_cmdsendEvnet:而不是my_sendEvent:

特殊写法

前几日在看到AFNetworking源码的时候突然想起之前看的某篇博客说AFNetworkingdataTaskresume方法好像也是经过方法交换的,想看一下它是怎么做的,是否是和我上面写的一样。于是就搜索了了一下method_exchangeImplementations方法,发现AFN并没有通过分类实现,于是我就看了下它的实现方法。

AFN的源码看起来比较绕,但剥离方法的封装之后,看起来就简单多了。比如想要交换上述的sendEvent:方法,并不需要写在分类中,而是随便写了一个类,在这个类内部处理。

+ (void)load {
    
    Method mySendEvent = class_getInstanceMethod([self class], @selector(my_sendEvent:));
    if(class_addMethod([UIApplication class], @selector(my_sendEvent:), method_getImplementation(mySendEvent), method_getTypeEncoding(mySendEvent))) {
     
        Method originalMethod = class_getInstanceMethod([UIApplication class], @selector(sendEvent:));
        Method swizzlingMethod = class_getInstanceMethod([UIApplication class], @selector(my_sendEvent:));
        method_exchangeImplementations(originalMethod, swizzlingMethod);   
    }    
}

- (void)my_sendEvent:(UIEvent *)event {
    
    [self my_sendEvent:event];
}

与之前的写法大致相同,唯一不同的是,将自己的实现方法my_sendEvent:写在了这个类中,并将这个方法用class_addMethod添加给了UIApplication。在自己的方法中打断点可以发现,这里的self并不是这个类的对象,而是UIApplication的对象。这个涉及到了OC底层objc_msgSend方法的原理,在这里就不细说了。

你可能感兴趣的:(OC 黑魔法之 Method Swizzling)