您的位置:首页 > 移动开发 > IOS开发

ios runtime IMP指针 消息转发机制

2016-05-13 08:47 531 查看
本文代码是根据消息转发机制来写的, 有不妥之处, 请大神指正

1. UIViewController (ViewDidLoadName)文件 UIViewController的category

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

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




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

ViewController.m文件

[objc]
view plain
copy
print?





<span style="font-size:18px;">//  ViewController.m  
//  MethodSwizzlingIMPdemo  
//  
//  Created by 帝炎魔 on 16/5/12.  
//  Copyright © 2016年 帝炎魔. All rights reserved.  
//  
  
#import "ViewController.h"  
#import "UIViewController+ViewDidLoadName.h"  
#import "Person.h"  
  
@interface ViewController ()  
  
@end  
  
@implementation ViewController  
  
- (void)viewDidLoad {  
    [super viewDidLoad];  
      
      
    // 创建Person对象 执行run方法, 但是Person类中并没有实现run方法  
    // 我们利用消息转发机制防止其崩溃,或者用其他方法来代替[per run]的方法  
    Person *per = [[Person alloc] init];  
    [per run];  
      
    // Do any additional setup after loading the view, typically from a nib.  
}  
  
  
  
- (void)didReceiveMemoryWarning {  
    [super didReceiveMemoryWarning];  
    // Dispose of any resources that can be recreated.  
}  
  
@end</span>  

UIViewController (ViewDidLoadName).h文件

[objc]
view plain
copy
print?





<span style="font-size:18px;">//  UIViewController+ViewDidLoadName.h  
//  MethodSwizzlingIMPdemo  
//  
//  Created by 帝炎魔 on 16/5/12.  
//  Copyright © 2016年 帝炎魔. All rights reserved.  
//  
  
#import <UIKit/UIKit.h>  
  
@interface UIViewController (ViewDidLoadName)  
  
@end</span>  

UIViewController (ViewDidLoadName).m文件

[objc]
view plain
copy
print?





<span style="font-size:18px;">//  UIViewController+ViewDidLoadName.m  
//  MethodSwizzlingIMPdemo  
//  
//  Created by 帝炎魔 on 16/5/12.  
//  Copyright © 2016年 帝炎魔. All rights reserved.  
//  
  
#import "UIViewController+ViewDidLoadName.h"  
#import <objc/runtime.h>  
  
// 有返回值的IMP  
typedef id (* _IMP) (id, SEL, ...);  
// 没有返回值的IMP(定义为VIMP)  
typedef void (* _VIMP) (id, SEL, ...);  
  
@implementation UIViewController (ViewDidLoadName)  
  
  
+(void)load  
{  
    // 保证交换方法只执行一次  
    static dispatch_once_t onceToken;  
    dispatch_once(&onceToken, ^{  
          
        // 获取原始方法  
        Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));  
        // 获取原始方法的实现指针(IMP)  
        _VIMP viewDidLoad_IMP = (_VIMP)method_getImplementation(viewDidLoad);  
          
        // 重新设置方法的实现  
        method_setImplementation(viewDidLoad, imp_implementationWithBlock(^(id target, SEL action) {  
            // 调用系统的原生方法  
            viewDidLoad_IMP(target, @selector(viewDidLoad));  
            // 新增的功能代码  
            NSLog(@"%@ did load", target);  
        }));  
          
    });  
}  
  
//+ (void)load  
//{  
//    // 保证交换方法只执行一次  
//    static dispatch_once_t onceToken;  
//    dispatch_once(&onceToken, ^{  
//        // 获取到这个类的viewDidLoad方法, 它的类型是一个objc_method结构体的指针  
//        Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));  
//          
//        // 获取到自己刚刚创建的一个方法  
//        Method myViewDidLoad = class_getInstanceMethod(self, @selector(myViewDidLoad));  
//          
//        // 交换两个方法的实现  
//        method_exchangeImplementations(viewDidLoad, myViewDidLoad);  
//          
//        
//          
//    });  
//}  
//  
//- (void)myViewDidLoad  
//{  
//    // 调用系统的方法  
//    [self myViewDidLoad];  
//    NSLog(@"%@ did load", self);  
//}  
  
@end</span>  

Person.h 文件

[objc]
view plain
copy
print?





<span style="font-size:18px;">//  Person.h  
//  MethodSwizzlingIMPdemo  
//  
//  Created by 帝炎魔 on 16/5/12.  
//  Copyright © 2016年 帝炎魔. All rights reserved.  
//  
  
#import <Foundation/Foundation.h>  
  
@interface Person : NSObject  
  
- (void)run;  
  
@end</span>  

Person.m文件

[objc]
view plain
copy
print?





<span style="font-size:18px;">//  Person.m  
//  MethodSwizzlingIMPdemo  
//  
//  Created by 帝炎魔 on 16/5/12.  
//  Copyright © 2016年 帝炎魔. All rights reserved.  
//  
  
#import "Person.h"  
#import <objc/runtime.h>  
#import "Car.h"  
  
@implementation Person  
  
/** 
 *  首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。 
     
    方案一: 
  
    + (BOOL)resolveInstanceMethod:(SEL)sel 
    + (BOOL)resolveClassMethod:(SEL)sel 
  
    方案二: 
  
    - (id)forwardingTargetForSelector:(SEL)aSelector 
  
    方案三: 
  
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; 
    - (void)forwardInvocation:(NSInvocation *)anInvocation; 
  
*/  
  
void run (id self, SEL _cmd)  
{  
    // 程序会走我们C语言的部分  
    NSLog(@"%@ %s", self, sel_getName(_cmd));  
}  
  
  
/** 
 *   方案一 
 * 
 *   为Person类动态增加了run方法的实现 
    由于没有实现run对应的方法, 那么系统会调用resolveInstanceMethod让你去做一些其他操作 
 */  
  
+ (BOOL)resolveInstanceMethod:(SEL)sel  
{  
//    if(sel == @selector(run)) {  
//        class_addMethod([self class], sel, (IMP)run, "v@:");  
//        return YES;  
//    }  
    return [super respondsToSelector:sel];  
}  
  
/** 方案二 
 *  现在不对方案一做任何的处理, 直接调用父类的方法 
    系统会走到forwardingTargetForSelector方法 
 */  
  
//- (id)forwardingTargetForSelector:(SEL)aSelector  
//{  
//    return [[Car alloc] init];  
//}  
  
  
/** 
 *   不实现forwardingTargetForSelector, 
     系统就会调用方案三的两个方法 
    methodSignatureForSelector 和 forwardInvocation 
 */  
  
/** 
 *  方案三 
    开头我们要找的错误unrecognized selector sent to instance原因,原来就是因为methodSignatureForSelector这个方法中,由于没有找到run对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。 
  
     所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。 
  */  
  
/** 
 *  methodSignatureForSelector 
 *  用来生成方法签名, 这个签名就是给forwardInvocation中参数NSInvocation调用的 
 * 
 */  
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector  
{  
    NSString *sel = NSStringFromSelector(aSelector);  
    // 判断你要转发的SEL  
    if ([sel isEqualToString:@"run"]) {  
        // 为你的转发方法手动生成签名  
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];  
          
    }  
    return [super methodSignatureForSelector:aSelector];  
}  
/** 
 *  关于生成签名类型"v@:"解释一下, 每个方法会默认隐藏两个参数, self, _cmd 
    self 代表方法调用者, _cmd 代表这个方法SEL, 签名类型就是用来描述这个方法的返回值, 参数的,  
    v代表返回值为void, @表示self, :表示_cmd 
 */  
-(void)forwardInvocation:(NSInvocation *)anInvocation  
{  
    SEL selector = [anInvocation selector];  
    // 新建需要转发消息的对象  
    Car *car = [[Car alloc] init];  
    if ([car respondsToSelector:selector]) {  
        // 唤醒这个方法  
        [anInvocation invokeWithTarget:car];  
    }  
}  
  
@end</span>  

Car .m文件

[objc]
view plain
copy
print?





<span style="font-size:18px;">//  Car.m  
//  MethodSwizzlingIMPdemo  
//  
//  Created by 帝炎魔 on 16/5/12.  
//  Copyright © 2016年 帝炎魔. All rights reserved.  
//  
  
#import "Car.h"  
  
@implementation Car  
  
  
- (void)run  
{  
     NSLog(@"%@ %s", self, sel_getName(_cmd));  
}  
@end</span> 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: