欢迎来到 IT实训基地-南通科迅教育
咨询电话:0513-81107100
ios runtime IMP指针 消息转发机制Demo
2016/11/23
科迅教育
268
南通网页设计培训学校如何选择-可免费试听

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

1. UIViewController (ViewDidLoadName)文件 UIViewController的category

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

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

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

ViewController.m文件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<span style="font-size:18px;">//  ViewController.m
//  MethodSwizzlingIMPdemo
 
#import"ViewController.h"
#import"UIViewController+ViewDidLoadName.h"
#import"Person.h"
 
@interfaceViewController ()
 
@end
 
@implementationViewController
 
- (void)viewDidLoad {
    [superviewDidLoad];
     
     
    // 创建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 {
    [superdidReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
 
@end</span>


 

UIViewController (ViewDidLoadName).h文件

 

?
1
2
3
4
5
6
7
8
9
<span style="font-size:18px;">//  UIViewController+ViewDidLoadName.h
//  MethodSwizzlingIMPdemo
//
 
#import<uikit uikit.h="">
 
@interfaceUIViewController (ViewDidLoadName)
 
@end</uikit></span>
UIViewController (ViewDidLoadName).m文件

 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<span style="font-size:18px;">//  UIViewController+ViewDidLoadName.m
//  MethodSwizzlingIMPdemo
//
 
#import"UIViewController+ViewDidLoadName.h"
#import<objc runtime.h="">
 
// 有返回值的IMP
typedef id (* _IMP) (id, SEL, ...);
// 没有返回值的IMP(定义为VIMP)
typedefvoid(* _VIMP) (id, SEL, ...);
 
@implementationUIViewController (ViewDidLoadName)
 
 
+(void)load
{
    // 保证交换方法只执行一次
    staticdispatch_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</objc></span>

Person.h 文件

 

 

?
1
2
3
4
5
6
7
8
9
10
11
<span style="font-size:18px;">//  Person.h
//  MethodSwizzlingIMPdemo
//
 
#import<foundation foundation.h="">
 
@interfacePerson : NSObject
 
- (void)run;
 
@end</foundation></span>

Person.m文件

 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<span style="font-size:18px;">//  Person.m
//  MethodSwizzlingIMPdemo
//
 
#import"Person.h"
#import<objc runtime.h="">
#import"Car.h"
 
@implementationPerson
 
/**
 *  首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。
     
    方案一:
  
    + (BOOL)resolveInstanceMethod:(SEL)sel
    + (BOOL)resolveClassMethod:(SEL)sel
  
    方案二:
  
    - (id)forwardingTargetForSelector:(SEL)aSelector
  
    方案三:
  
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
  
*/
 
voidrun (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[superrespondsToSelector: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[supermethodSignatureForSelector: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</objc></span>

Car .m文件

<span style="font-size:18px;">// Car.m

?
1
2
3
4
5
6
7
8
9
10
11
12
13
//  MethodSwizzlingIMPdemo
//
 
#import"Car.h"
 
@implementationCar
 
 
- (void)run
{
     NSLog(@"%@ %s", self, sel_getName(_cmd));
}
@end</span>
77
关闭
先学习,后交费申请表
每期5位名额
在线咨询
免费电话
QQ联系
先学习,后交费
TOP
您好,您想咨询哪门课程呢?
关于我们
机构简介
官方资讯
地理位置
联系我们
0513-91107100
周一至周六     8:30-21:00
微信扫我送教程
手机端访问
南通科迅教育信息咨询有限公司     苏ICP备15009282号     联系地址:江苏省南通市人民中路23-6号新亚大厦三楼             法律顾问:江苏瑞慈律师事务所     Copyright 2008-