Runtime基础使用场景-拦截替换方法(class_addMethod ,class_replaceMethod和method_exchangeImplementations)

前话

这几天在系统的学习 runtime,在学习 runtime 的基础使用案例中,"方法替换"这种使用情况下,发现有两种写法. 其实也不是两种写法,准确的来说一种是比较严谨的,另一种则没有那么严谨.

发现这两种写法的差异后,我主要集中在下列:

  • class_addMethod
  • class_replaceMethod
  • method_exchangeImplementations

哪个方法的具体作用.

下面,这篇文章就这两种写法和上述三种方法的区别.

第一种写法

《OC最实用的runtime总结,面试、工作你看我就足够了!》的时候,它里边的写法是简单的获取到被替换和替换方法的Method.然后直接使用method_exchangeImplementations进行方法的替换. 最开始使用的时候,因为测试范例比较简单,所以并没有发现这样写的弊端.但是确实能够实现方法替换的效果. 代码如下:

 
  1. +(void)load{

  2. //获取两个类的方法

  3. Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));

  4. Method m2 = class_getClassMethod([UIImage class], @selector(ll_imageName:));

  5. //开始交换方法实现

  6. method_exchangeImplementations(m1, m2);

  7. }

在后来看到《runtime详解》的时候,发现作者的写法并不是这样,虽然作者添加少量注释,但是愚钝的我还没有想清楚,这也是这篇文章的初衷,也是下一小结的由来.

第二种写法

上一节的这种情况虽然能够实现我们想要的效果.但是我们有没有想过这种情况:

" 周全起见,有两种情况要考虑一下。第一种情况是要复写的方法(overridden)并没有在目标类中实现(notimplemented),而是在其父类中实现了。第二种情况是这个方法已经存在于目标类中(does existin the class itself)。这两种情况要区别对待。 (译注: 这个地方有点要明确一下,它的目的是为了使用一个重写的方法替换掉原来的方法。但重写的方法可能是在父类中重写的,也可能是在子类中重写的。) 对于第一种情况,应当先在目标类增加一个新的实现方法(override),然后将复写的方法替换为原先(的实现(original one)。 对于第二情况(在目标类重写的方法)。这时可以通过method_exchangeImplementations来完成交换."

---- 以上来自:《Objective-C的方法替换》

 
  1. +(void)load{

  2. NSString *className = NSStringFromClass(self.class);

  3. NSLog(@"classname %@", className);

  4. static dispatch_once_t onceToken;

  5. dispatch_once(&onceToken, ^{

  6. //要特别注意你替换的方法到底是哪个性质的方法

  7. // When swizzling a Instance method, use the following:

  8. // Class class = [self class];

  9. // When swizzling a class method, use the following:

  10. Class class = object_getClass((id)self);

  11. SEL originalSelector = @selector(systemMethod_PrintLog);

  12. SEL swizzledSelector = @selector(ll_imageName);

  13. Method originalMethod = class_getInstanceMethod(class, originalSelector);

  14. Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

  15. BOOL didAddMethod =

  16. class_addMethod(class,

  17. originalSelector,

  18. method_getImplementation(swizzledMethod),

  19. method_getTypeEncoding(swizzledMethod));

  20. if (didAddMethod) {

  21. class_replaceMethod(class,

  22. swizzledSelector,

  23. method_getImplementation(originalMethod),

  24. method_getTypeEncoding(originalMethod));

  25. } else {

  26. method_exchangeImplementations(originalMethod, swizzledMethod);

  27. }

  28. });

  29. }

解析:

上面提到的:

dispatch_once这里不是“单例”,是保证方法替换只执行一次.

说明:

systemMethod_PrintLog:被替换方法ll_imageName:替换方法

class_addMethod:如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现

1.如果返回成功:则说明被替换方法没有存在.也就是被替换的方法没有被实现,我们需要先把这个方法实现,然后再执行我们想要的效果,用我们自定义的方法去替换被替换的方法. 这里使用到的是class_replaceMethod这个方法. class_replaceMethod本身会尝试调用class_addMethodmethod_setImplementation,所以直接调用class_replaceMethod就可以了)

2.如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换即

另外:

  • 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP
  • 我们可以利用 class_replaceMethod 来修改类
  • 我们可以利用 method_setImplementation 来直接设置某个方法的IMP

其实我们如果 研究过 AFN 代码的话,会发现, AFN 就是第二种写法.在AFURLSessionManager.m的第296行:

 
  1. static inline void af_swizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector) {

  2. Method originalMethod = class_getInstanceMethod(class, originalSelector);

  3. Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

  4. if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {

  5. class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));

  6. } else {

  7. method_exchangeImplementations(originalMethod, swizzledMethod);

  8. }

  9. }

详尽的代码请查看 Demo.

下载地址

具体的 Demo 代码可以在我的 GitHub 上找到 Demo地址

其它

关于 load 的调用次数问题,大家可以查看这两篇文章.+(void)load和+(void)initialize可当做普通类方法(Class Method)调用的.《NSObject的load和initialize方法!》《Objective C类方法load和initialize的区别》

参考文章

  1. 《OC最实用的runtime总结,面试、工作你看我就足够了!》
  2. 《Objective-C的方法替换》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值