Objective-C的hook方案(一): Method Swizzling

来源:互联网 发布:淘宝店铺上传宝贝软件 编辑:程序博客网 时间:2024/05/01 14:01
Objective-C的hook方案(一):  Method Swizzling


在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法吗?在Objective-C编程中,如何实现hook呢?标题有点大,计划分几篇来总结。

本文主要介绍针对selector的hook,主角被标题剧透了———— Method Swizzling 。


Method Swizzling 原理


在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。

每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。





我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,

我们可以利用 class_replaceMethod 来修改类,

我们可以利用 method_setImplementation 来直接设置某个方法的IMP,
……

归根结底,都是偷换了selector的IMP,如下图所示:


Method Swizzling 实践



举个例子好了,我想钩一下NSArray的lastObject 方法,只需两个步骤。

第一步:给NSArray加一个我自己的lastObject

  1. #import "NSArray+Swizzle.h"  
  2.   
  3.   
  4. @implementation NSArray (Swizzle)  
  5.   
  6.   
  7. - (id)myLastObject  
  8. {  
  9.     id ret = [self myLastObject];  
  10.     NSLog(@"**********  myLastObject *********** ");  
  11.     return ret;  
  12. }  
  13. @end  
乍一看,这不递归了么?别忘记这是我们准备调换IMP的selector,[self myLastObject] 将会执行真的 [self lastObject] 。

第二步:调换IMP

  1. #import <objc/runtime.h>  
  2. #import "NSArray+Swizzle.h"  
  3.   
  4.   
  5. int main(int argc, char *argv[])  
  6. {  
  7.     @autoreleasepool {  
  8.           
  9.         Method ori_Method =  class_getInstanceMethod([NSArray class], @selector(lastObject));  
  10.         Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));  
  11.         method_exchangeImplementations(ori_Method, my_Method);  
  12.           
  13.         NSArray *array = @[@"0",@"1",@"2",@"3"];  
  14.         NSString *string = [array lastObject];  
  15.         NSLog(@"TEST RESULT : %@",string);  
  16.           
  17.         return 0;  
  18.     }  
  19. }  
控制台输出Log:

  1. 2013-07-18 16:26:12.585 Hook[1740:c07] **********  myLastObject ***********   
  2. 2013-07-18 16:26:12.589 Hook[1740:c07] TEST RESULT : 3  
结果很让人欣喜,是不是忍不住想给UIWebView的loadRequest: 加 TODO 了呢? 

Method Swizzling 的封装



之前在github上找到的RNSwizzle,推荐给大家,可以搜一下。

[cpp] view plain copy
 print?
  1. //  
  2. //  RNSwizzle.m  
  3. //  MethodSwizzle  
  4.   
  5.   
  6. #import "RNSwizzle.h"  
  7. #import <objc/runtime.h>  
  8. @implementation NSObject (RNSwizzle)  
  9.   
  10.   
  11. + (IMP)swizzleSelector:(SEL)origSelector   
  12.                withIMP:(IMP)newIMP {  
  13.   Class class = [self class];  
  14.   Method origMethod = class_getInstanceMethod(class,  
  15.                                               origSelector);  
  16.   IMP origIMP = method_getImplementation(origMethod);  
  17.     
  18.   if(!class_addMethod(self, origSelector, newIMP,  
  19.                       method_getTypeEncoding(origMethod)))  
  20.   {  
  21.     method_setImplementation(origMethod, newIMP);  
  22.   }  
  23.     
  24.   return origIMP;  
  25. }  
  26. @end  

Method Swizzling 危险不危险


针对这个问题,我在stackoverflow上看到了满意的答案,这里翻译一下,总结记录在本文中,以示分享:


使用 Method Swizzling 编程就好比切菜时使用锋利的刀,一些人因为担心切到自己所以害怕锋利的刀具,可是事实上,使用钝刀往往更容易出事,而利刀更为安全。
Method swizzling 可以帮助我们写出更好的,更高效的,易维护的代码。但是如果滥用它,也将会导致难以排查的bug。 


背景


好比设计模式,如果我们摸清了一个模式的门道,使用该模式与否我们自己心里有数。单例模式就是一个很好的例子,它饱受争议但是许多人依旧使用它。Method Swizzling也是一样,一旦你真正理解它的优势和弊端,使用它与否你应该就有你自己的观点。

讨论


这里是一些 Method Swizzling的陷阱:
  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method's arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug


我将逐一分析这些点,增进对Method Swizzling的理解的同时,并搞懂如何应对。


http://blog.csdn.net/yiyaaixuexi/article/details/9374411 详情请看念茜的博客







0 0