说说objcRuntime的一些妙用(class_addMethod,class_replaceMethod)

前言:陈列一下今天要讲的知识点:class_addMethod,class_replaceMethod,method_getImplementation,object_getClass

涉及到的知识

》》使用category,通过Runtime实现用自己的函数调换掉原生函数

》》oc的message forwarding

》》使用Runtime为类添加原来没有的方法

》》为什么category里不重写方法


注明:本文章内技术参考当然来自四面八方,来自不同时期,小弟只是做个总结,有不好的地方欢迎大家指导


先从一个场景问题带出吧,毕业设计的时候小弟做ipad应用,到后面才决定加上旋转屏适配,看着100多个文件20多个页面差点没把血吐出来,哈哈每个controller去修改方法是不可能的了,因为强迫症也不想多创个父类,好吧决定一次过替换掉这些controller里的viewWillAppear:  和  willAnimateRotationToInterfaceOrientation:duration:,换成自己的。


先看一个category

通过运用class_addMethod和class_replaceMethod来调换掉系统库里的方法

[objc]  view plain  copy
  1. #import "NSObject+Swizzle.h"  
  2.   
  3. @implementation NSObject (Swizzle)  
  4.   
  5. + (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {  
  6.       
  7.     Method originMethod = class_getInstanceMethod(self, origSel);  
  8.     Method newMethod = class_getInstanceMethod(self, aftSel);  
  9.       
  10.     if(originMethod && newMethod) {//必须两个Method都要拿到  
  11.         if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {  
  12.             //实现成功添加后  
  13.             class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));  
  14.         }  
  15.         return YES;  
  16.     }  
  17.     return NO;  
  18. }  
  19. @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做载体来演示吧:

[objc]  view plain  copy
  1. #import "MyString.h"  
  2. #import "NSObject+Swizzle.h"  
  3.   
  4. @implementation MyString  
  5.   
  6. + (void)load {  
  7.     static dispatch_once_t onceToken;  
  8.     dispatch_once(&onceToken, ^{  
  9.         Class clazz = object_getClass((id)self);  
  10.         [clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];  
  11.     });  
  12. }  
  13.   
  14. + (BOOL)myResolveInstanceMethod:(SEL)sel {  
  15.       
  16.     if(! [self myResolveInstanceMethod:sel]) {  
  17.         NSString *selString = NSStringFromSelector(sel);  
  18.         if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {  
  19.             class_addMethod(self, sel, class_getMethodImplementation(self@selector(dynamicMethodIMP)), "v@:");  
  20.             return YES;  
  21.         }else {  
  22.             return NO;  
  23.         }  
  24.     }  
  25.     return YES;  
  26. }  
  27.   
  28. - (void)dynamicMethodIMP {  
  29.     NSLog(@"我是动态加入的函数");  
  30. }  
  31.   
  32. @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:去替换原生的,好了,现在如果我们在运行时调用了一个不存在的方法,系统会去调用我们的my ResolveInstanceMethod:,是的不用怀疑。

3.现在看看myResolveInstanceMethod:里面又调用了一次myResolveInstanceMethod:,有的朋友会以为是递归其实并不是,系统去调用原生的方法,会跑到我们自己的方法实现,是因为我们之前的swizzle操作没问题,而不要忘记了,我们自己的方法selector对应的实现,已经换成了原生方法的实现,ok。。if(! [self myResolveInstanceMethod:sel])是调用原生方法的实现,去检测一次传入的方法是否存在,如果还是没有,则做class_addMethod操作为此类添加对应的方法,return YES,该方法被系统调用,OK,达到目的。

-------这里补充一个知识点class_addMethod参数的意义,按顺序是,类--选择子--实现--方法的返回值和参数资料。v代表返回值void,@代表id类型对象,:代表选择子。为什么呢,其实每一个oc方法都有两个隐式的参数(id self, SEL _cmd)也可以说是由C语言函数再加着两个参数组成一个oc方法。ok,疑问又解决了。


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

[objc]  view plain  copy
  1. NSLog(@"begin test");  
  2. //---------------------------------------------------------------  
  3.       
  4.     MyString *string = [[MyString alloc] init];  
  5.     [string performSelector:@selector(countAll)];  
  6.     [string performSelector:@selector(pushViewController)];  
[objc]  view plain  copy
  1. <pre name="code" class="objc">//---------------------------------------------------------------  
  2.     NSLog(@"finish test");  

 
 

-----Log:

[javascript]  view plain  copy
  1. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] begin test  
  2. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数  
  3. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数  
  4. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数  
  5. 2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值