object-C黑科技之Runtime机制利用

在window上中具有hook的概念,IOS由于沙箱的保护机制,是很难hook系统API对其他应用程序的影响的,不过在我们本身的APP中,可以利用OC的Runtime机制实现运行时HOOK,也就是动态增加,改变,替换系统的API或者其他类的私有方法,怎么样?是不是很酷?接下来凝酷科技就为大家来抛砖引玉,展现一下润runtime机制的无穷魅力。

本文使用的Runtime机制使用到几个主要的方法:class_addMethod,class_replaceMethod,method_getImplementation,object_getClass。

  1. 那么为什么使用Runtime,没有其他替换方案吗?一会我会说明如下的知识点:
    本文通过category对原生类API,运用Runtime机制实现自定义函数调换掉原生函数。
    实现原理要依赖于 oc的message forwarding机制。(寻找运行时方法的机制)
    使用Runtime为类添加原来没有的方法
    为什么不通过category里重写方法而达到目的呢?

那么RunTime到底有什么应用场景呢?其实场景可多了,可以说是修改大项目的一些很棘手的问题的必备法宝。比方说:我想替换掉项目中所有UILabel的字体,那么怎么办?不能一个一个文件修改吧?所以这个时候利用运行时机制在UIViewController的类别中替换viewWillAppear:(BOOL)animated为自己的。

例子:一个category

通过运用class_addMethodclass_replaceMethod来调换掉系统库里的方法

  1. #import "NSObject+Swizzle.h"
  2. @implementation NSObject (Swizzle)
  3. + (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {
  4.     Method originMethod = class_getInstanceMethod(self, origSel);
  5.     Method newMethod = class_getInstanceMethod(self, aftSel);
  6.     if(originMethod && newMethod) {//必须两个Method都要拿到
  7.         if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
  8.             //实现成功添加后
  9.             class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
  10.         }
  11.         return YES;
  12.     }
  13.     return NO;
  14. }
  15. @end

1.传入两个参数,原方法选择子,新方法选择子,并通过class_getInstanceMethod()拿到对应的Method

2.class_addMethod,是相对于实现来的说的,将本来不存在于被操作的Class里的newMethod的实现添加在被操作的Class里,并使用origSel作为其选择子(注意参数中的self为被操作的Class,不要忘了这里是类方法).

3.class_replaceMethod,addMethod成功完成后,从参数可以看出,目的是换掉method_getImplaementation(roiginMethod)的选择子,将原方法的实现的SEL换成新方法的SEL:aftSel,ok目的达成了。想一想,现在通过旧方法SEL来调用,就会实现新方法的IMP,通过新方法的SEL来调用,就会实现旧方法的IMP,好了理一理思路继续往下。

这次用NSString做载体来演示吧:

  1. #import "MyString.h"
  2. #import "NSObject+Swizzle.h"
  3. @implementation MyString
  4. + (void)load {
  5.     static dispatch_once_t onceToken;
  6.     dispatch_once(&onceToken, ^{
  7.         Class clazz = object_getClass((id)self);
  8.         [clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
  9. //这里已经实现了方法替换,resolveInstanceMethod会调用myResolveInstanceMethod
  10.     });
  11. }
  12. + (BOOL)myResolveInstanceMethod:(SEL)sel {
  13.     if(! [self myResolveInstanceMethod:sel]) {
  14.        //系统的调用被hook过来,继续调用myResolveInstanceMethod是回调系统的原生方法
  15.         NSString *selString = NSStringFromSelector(sel);
  16.         if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {
  17.             class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");
  18.             return YES;
  19.         }else {
  20.             return NO;
  21.         }
  22.     }
  23.     return YES;
  24. }
  25. - (void)dynamicMethodIMP {
  26.     NSLog(@"我是动态加入的函数");
  27. }
  28. @end

1. 首先这里要说一下resolveInstanceMethod 方法,这是oc的message forwarding机制,当运行时对象调用了一个找不到的方法的时候系统会去寻找的方法,这个方法是第一步就执行的地方,我们可以在这里面runtime添加方法,首先我们得劫持这个方法,做我们自己的事,通过刚才category里封装好的swizzleMethod:withMethod:

  • 这个时候有朋友有疑问了,我们可以重写这个方法来做自己的事情啊,其实并不可以,在category里重写现有方法会有警告#Category is implementing a method which will also be implemented by its primary class,这种做法是不提倡的!
  • category没有办法去代替子类,它不能像子类一样通过super去调用父类的方法实现。如果category中重写覆盖了当前类中的某个方法,那么这个当前类中的原始方法实现,将永远不会被执行,这在某些方法里是致命的(这里提一下一个特例+(void)load,它会在当前方法里执行完再去category里执行).
  • 如果两个category重写了同一个方法,我们无法控制哪个优先级更高,一直以来还是提倡通过继承去重写方法

2. object_getClass拿到当前MyString的Class,调用刚才category里封装好的swizzleMethod:withMethod:,用我们自己的myResolveInstanceMethod:去替换原生的,好了,现在如果我们在运行时调用了一个不存在的方法,系统会去调用我们的myResolveInstanceMethod:,是的不用怀疑。
3.现在看看myResolveInstanceMethod:里面又调用了一次myResolveInstanceMethod:,有的朋友会以为是递归其实并不是,系统去调用原生的方法,会跑到我们自己的方法实现,是因为我们之前的swizzle操作没问题,而不要忘记了,我们自己的方法selector对应的实现,已经换成了原生方法的实现,ok。。if(! [self myResolveInstanceMethod:sel])是调用原生方法的实现,去检测一次传入的方法是否存在,如果还是没有,则做class_addMethod操作为此类添加对应的方法,return YES,该方法被系统调用,OK,达到目的。
class_addMethod参数的意义

class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)

class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");

按顺序是,类--选择子--实现--方法的返回值和参数资料。
v代表返回值void,@代表id类型对象,:代表选择子。
why? 其实每一个oc方法都有两个隐式的参数(id self, SEL _cmd),也可以说是由C语言函数再加着两个参数组成一个oc方法。

最后看看我们的工作的收获:

NSLog(@"test-begin");
    MyString *string = [[MyString alloc] init];
    [string performSelector:@selector(countAll)];
    [string performSelector:@selector(pushViewController)];
<pre name="code" class="objc">
    NSLog(@"test-end");
Log:
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] test-begin
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] test-end

 

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: