ios runtime IMP指针 消息转发机制

本文代码是根据消息转发机制来写的, 有不妥之处, 请大神指正

1. UIViewController (ViewDidLoadName)文件 UIViewController的category


在实现viewDidLoad系统方法的前提下 添加自定义的方法


2. Person类有一个run的方法(没有实现),这里展示了OC中的消息转发机制, 使其不崩溃并实现方法,或者转到Car的run方法来实现


直接上代码(注释很全, 简单易懂)


ViewController.m文件

  1. <span style="font-size:18px;">//  ViewController.m  
  2. //  MethodSwizzlingIMPdemo  
  3. //  
  4. //  Created by 帝炎魔 on 16/5/12.  
  5. //  Copyright © 2016年 帝炎魔. All rights reserved.  
  6. //  
  7.   
  8. #import "ViewController.h"  
  9. #import "UIViewController+ViewDidLoadName.h"  
  10. #import "Person.h"  
  11.   
  12. @interface ViewController ()  
  13.   
  14. @end  
  15.   
  16. @implementation ViewController  
  17.   
  18. - (void)viewDidLoad {  
  19.     [super viewDidLoad];  
  20.       
  21.       
  22.     // 创建Person对象 执行run方法, 但是Person类中并没有实现run方法  
  23.     // 我们利用消息转发机制防止其崩溃,或者用其他方法来代替[per run]的方法  
  24.     Person *per = [[Person alloc] init];  
  25.     [per run];  
  26.       
  27.     // Do any additional setup after loading the view, typically from a nib.  
  28. }  
  29.   
  30.   
  31.   
  32. - (void)didReceiveMemoryWarning {  
  33.     [super didReceiveMemoryWarning];  
  34.     // Dispose of any resources that can be recreated.  
  35. }  
  36.   
  37. @end</span>  


UIViewController (ViewDidLoadName).h文件

  1. <span style="font-size:18px;">//  UIViewController+ViewDidLoadName.h  
  2. //  MethodSwizzlingIMPdemo  
  3. //  
  4. //  Created by 帝炎魔 on 16/5/12.  
  5. //  Copyright © 2016年 帝炎魔. All rights reserved.  
  6. //  
  7.   
  8. #import <UIKit/UIKit.h>  
  9.   
  10. @interface UIViewController (ViewDidLoadName)  
  11.   
  12. @end</span>  
UIViewController (ViewDidLoadName).m文件
  1. <span style="font-size:18px;">//  UIViewController+ViewDidLoadName.m  
  2. //  MethodSwizzlingIMPdemo  
  3. //  
  4. //  Created by 帝炎魔 on 16/5/12.  
  5. //  Copyright © 2016年 帝炎魔. All rights reserved.  
  6. //  
  7.   
  8. #import "UIViewController+ViewDidLoadName.h"  
  9. #import <objc/runtime.h>  
  10.   
  11. // 有返回值的IMP  
  12. typedef id (* _IMP) (idSEL, ...);  
  13. // 没有返回值的IMP(定义为VIMP)  
  14. typedef void (* _VIMP) (idSEL, ...);  
  15.   
  16. @implementation UIViewController (ViewDidLoadName)  
  17.   
  18.   
  19. +(void)load  
  20. {  
  21.     // 保证交换方法只执行一次  
  22.     static dispatch_once_t onceToken;  
  23.     dispatch_once(&onceToken, ^{  
  24.           
  25.         // 获取原始方法  
  26.         Method viewDidLoad = class_getInstanceMethod(self@selector(viewDidLoad));  
  27.         // 获取原始方法的实现指针(IMP)  
  28.         _VIMP viewDidLoad_IMP = (_VIMP)method_getImplementation(viewDidLoad);  
  29.           
  30.         // 重新设置方法的实现  
  31.         method_setImplementation(viewDidLoad, imp_implementationWithBlock(^(id target, SEL action) {  
  32.             // 调用系统的原生方法  
  33.             viewDidLoad_IMP(target, @selector(viewDidLoad));  
  34.             // 新增的功能代码  
  35.             NSLog(@"%@ did load", target);  
  36.         }));  
  37.           
  38.     });  
  39. }  
  40.   
  41. //+ (void)load  
  42. //{  
  43. //    // 保证交换方法只执行一次  
  44. //    static dispatch_once_t onceToken;  
  45. //    dispatch_once(&onceToken, ^{  
  46. //        // 获取到这个类的viewDidLoad方法, 它的类型是一个objc_method结构体的指针  
  47. //        Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));  
  48. //          
  49. //        // 获取到自己刚刚创建的一个方法  
  50. //        Method myViewDidLoad = class_getInstanceMethod(self, @selector(myViewDidLoad));  
  51. //          
  52. //        // 交换两个方法的实现  
  53. //        method_exchangeImplementations(viewDidLoad, myViewDidLoad);  
  54. //          
  55. //        
  56. //          
  57. //    });  
  58. //}  
  59. //  
  60. //- (void)myViewDidLoad  
  61. //{  
  62. //    // 调用系统的方法  
  63. //    [self myViewDidLoad];  
  64. //    NSLog(@"%@ did load", self);  
  65. //}  
  66.   
  67. @end</span>  

Person.h 文件
  1. <span style="font-size:18px;">//  Person.h  
  2. //  MethodSwizzlingIMPdemo  
  3. //  
  4. //  Created by 帝炎魔 on 16/5/12.  
  5. //  Copyright © 2016年 帝炎魔. All rights reserved.  
  6. //  
  7.   
  8. #import <Foundation/Foundation.h>  
  9.   
  10. @interface Person : NSObject  
  11.   
  12. - (void)run;  
  13.   
  14. @end</span>  

Person.m文件
  1. <span style="font-size:18px;">//  Person.m  
  2. //  MethodSwizzlingIMPdemo  
  3. //  
  4. //  Created by 帝炎魔 on 16/5/12.  
  5. //  Copyright © 2016年 帝炎魔. All rights reserved.  
  6. //  
  7.   
  8. #import "Person.h"  
  9. #import <objc/runtime.h>  
  10. #import "Car.h"  
  11.   
  12. @implementation Person  
  13.   
  14. /** 
  15.  *  首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。 
  16.      
  17.     方案一: 
  18.   
  19.     + (BOOL)resolveInstanceMethod:(SEL)sel 
  20.     + (BOOL)resolveClassMethod:(SEL)sel 
  21.   
  22.     方案二: 
  23.   
  24.     - (id)forwardingTargetForSelector:(SEL)aSelector 
  25.   
  26.     方案三: 
  27.   
  28.     - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; 
  29.     - (void)forwardInvocation:(NSInvocation *)anInvocation; 
  30.   
  31. */  
  32.   
  33. void run (id selfSEL _cmd)  
  34. {  
  35.     // 程序会走我们C语言的部分  
  36.     NSLog(@"%@ %s"self, sel_getName(_cmd));  
  37. }  
  38.   
  39.   
  40. /** 
  41.  *   方案一 
  42.  * 
  43.  *   为Person类动态增加了run方法的实现 
  44.     由于没有实现run对应的方法, 那么系统会调用resolveInstanceMethod让你去做一些其他操作 
  45.  */  
  46.   
  47. + (BOOL)resolveInstanceMethod:(SEL)sel  
  48. {  
  49. //    if(sel == @selector(run)) {  
  50. //        class_addMethod([self class], sel, (IMP)run, "v@:");  
  51. //        return YES;  
  52. //    }  
  53.     return [super respondsToSelector:sel];  
  54. }  
  55.   
  56. /** 方案二 
  57.  *  现在不对方案一做任何的处理, 直接调用父类的方法 
  58.     系统会走到forwardingTargetForSelector方法 
  59.  */  
  60.   
  61. //- (id)forwardingTargetForSelector:(SEL)aSelector  
  62. //{  
  63. //    return [[Car alloc] init];  
  64. //}  
  65.   
  66.   
  67. /** 
  68.  *   不实现forwardingTargetForSelector, 
  69.      系统就会调用方案三的两个方法 
  70.     methodSignatureForSelector 和 forwardInvocation 
  71.  */  
  72.   
  73. /** 
  74.  *  方案三 
  75.     开头我们要找的错误unrecognized selector sent to instance原因,原来就是因为methodSignatureForSelector这个方法中,由于没有找到run对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。 
  76.   
  77.      所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。 
  78.   */  
  79.   
  80. /** 
  81.  *  methodSignatureForSelector 
  82.  *  用来生成方法签名, 这个签名就是给forwardInvocation中参数NSInvocation调用的 
  83.  * 
  84.  */  
  85. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector  
  86. {  
  87.     NSString *sel = NSStringFromSelector(aSelector);  
  88.     // 判断你要转发的SEL  
  89.     if ([sel isEqualToString:@"run"]) {  
  90.         // 为你的转发方法手动生成签名  
  91.         return [NSMethodSignature signatureWithObjCTypes:"v@:"];  
  92.           
  93.     }  
  94.     return [super methodSignatureForSelector:aSelector];  
  95. }  
  96. /** 
  97.  *  关于生成签名类型"v@:"解释一下, 每个方法会默认隐藏两个参数, self, _cmd 
  98.     self 代表方法调用者, _cmd 代表这个方法SEL, 签名类型就是用来描述这个方法的返回值, 参数的,  
  99.     v代表返回值为void, @表示self, :表示_cmd 
  100.  */  
  101. -(void)forwardInvocation:(NSInvocation *)anInvocation  
  102. {  
  103.     SEL selector = [anInvocation selector];  
  104.     // 新建需要转发消息的对象  
  105.     Car *car = [[Car alloc] init];  
  106.     if ([car respondsToSelector:selector]) {  
  107.         // 唤醒这个方法  
  108.         [anInvocation invokeWithTarget:car];  
  109.     }  
  110. }  
  111.   
  112. @end</span>  

Car .m文件
  1. <span style="font-size:18px;">//  Car.m  
  2. //  MethodSwizzlingIMPdemo  
  3. //  
  4. //  Created by 帝炎魔 on 16/5/12.  
  5. //  Copyright © 2016年 帝炎魔. All rights reserved.  
  6. //  
  7.   
  8. #import "Car.h"  
  9.   
  10. @implementation Car  
  11.   
  12.   
  13. - (void)run  
  14. {  
  15.      NSLog(@"%@ %s"self, sel_getName(_cmd));  
  16. }  
  17. @end</span> 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值