Runtime那些事儿(消息机制)

一、关于runtime

之前在项目中有遇到过用runtime解决改变全局字体的问题,所以再一次感受到了runtime黑魔法的强大,趁现在有机会分享一下对runtime的一些理解。在对象调用方法是Objective-C中经常使用的功能,也就是消息的传递,而Objective-C是C的超集,所以和C不同的是,Objective-C使用的是动态绑定,也就是runtime。Objective-C的消息传递和消息机制也就不多说了,今天主要说的是动态方法,也就是函数的调用。

二、相关的几个函数

下面一张图详细的概括了每个函数调用的先后以及执行的前提

消息传递函数的调用

1.对象在收到无法解读的消息后,首先会调用所属类的


1

+ (BOOL)resolveInstanceMethod:(SEL)sel

这个方法在运行时,没有找到SEL的IML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。根据文档,如果实现了添加函数代码则返回YES,未实现返回NO。举个例子,新建了一个工程,首先我在ViewController这个类中执行doSomething1这个方法,代码如下


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

//

//  ViewController.m

//  RuntimeTest1

//

//  Created by HenryCheng on 15/12/24.

//  Copyright ?(版权符号) 2015年 www.igancao.com  All rights reserved.

//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    [self performSelector:@selector(doSomething)];

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end

运行结果


1

2

3

**2015-12-24 10:35:37.726 RuntimeTest1[1877:337842] -[ViewController doSomething]: unrecognized selector sent to instance 0x7fe9f3736680**

**2015-12-24 10:35:37.729 RuntimeTest1[1877:337842] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException‘, reason: ‘-[ViewController doSomething]: unrecognized selector sent to instance 0x7fe9f3736680‘**

***** First throw call stack:**

不出意外,程序崩溃,因为没有找到doSomething这个方法,下面我们在里面实现 + (BOOL)resolveInstanceMethod:(SEL)sel这个方法,并且判断如果SEL 是doSomething那就输出add method here


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

//

//  ViewController.m

//  RuntimeTest1

//

//  Created by HenryCheng on 15/12/24.

//  Copyright ?(版权符号) 2015年 www.igancao.com All rights reserved.

//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    [self performSelector:@selector(doSomething)];

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(doSomething)) {

        NSLog(@"add method here");

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end

继续运行,然后看到log


1

2

3

4

**2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] add method here**

**2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] -[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0**

**2015-12-24 10:47:24.690 RuntimeTest1[2007:382077] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException‘, reason: ‘-[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0‘**

***** First throw call stack:**

可以看到程序依然是崩溃了,但是我们可以看到输出了add method here,这说明我们 + (BOOL)resolveInstanceMethod:(SEL)sel这个方法执行了,并进入了判断,所以,在这儿,我们可以做一下操作,使这个方法得到相应,不至于走到最后- (void)doesNotRecognizeSelector:(SEL)aSelector这个方法中而崩掉了,接下来,我么继续操作,如下


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

//

//  ViewController.m

//  RuntimeTest1

//

//  Created by HenryCheng on 15/12/24.

//  Copyright ?(版权符号) 2015年 www.igancao.com All rights reserved.

//

#import "ViewController.h"

#import [objc/runtime.h](因识别问题,此处将尖括号改为方括号)

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    [self performSelector:@selector(doSomething)];

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(doSomething)) {

        NSLog(@"add method here");

        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "[email protected]:");

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

void dynamicMethodIMP (id self, SEL _cmd) {

    NSLog(@"doSomething SEL");

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end

导入了并且在+ (BOOL)resolveInstanceMethod:(SEL)sel中执行了class_addMethod这个方法,然后定义了一个void dynamicMethodIMP (id self, SEL _cmd)这个函数,运行工程,看log


1

2

**2015-12-24 11:45:11.934 RuntimeTest1[2284:478571] add method here**

**2015-12-24 11:45:11.934 RuntimeTest1[2284:478571] doSomething SEL**

这时候我们发现,程序并没有崩溃,而且还输出了doSomething SEL,这时候就说明我们已经通过runtime成功的向我们这个类中添加了一个方法。关于class_addMethod这个方法,是这样定义的


1

OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,  const char *types)

  • cls   在这个类中添加方法,也就是方法所添加的类
  • name  方法名,这个可以随便起的
  • imp   实现这个方法的函数
  • types 定义该数返回值类型和参数类型的字符串,这里比如"[email protected]:",其中v就是void,带表返回类型就是空,@代表参数,这里指的是id(self),这里:指的是方法SEL(_cmd),比如我再定义一个函数

1

2

3

4

int newMethod (id self, SEL _cmd, NSString *str) {

  return 100;

}

那么添加这个函数的方法就应该是ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "[email protected]:@");

2.如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法

消息继续往下传递到- (id)forwardingTargetForSelector:(SEL)aSelector看看是不是有对象可以执行这个方法,我们来重新建个工程,然后新建一个叫SecondViewController的类,里面有一个- (void)secondVCMethod方法,如下


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

//

//  SecondViewController.m

//  RuntimeTest2

//

//  Created by HenryCheng on 15/12/24.

//  Copyright ?(版权符号) 2015年  www.igancao.com All rights reserved.

//

#import "SecondViewController.h"

@interface SecondViewController ()

@end

@implementation SecondViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view.

}

- (void)secondVCMethod {

    NSLog(@"This is secondVC method !");

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

/*

#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    // Get the new view controller using [segue destinationViewController].

    // Pass the selected object to the new view controller.

}

*/

@end

工程结构应该是这样的

工程目录图

现在我想在ViewController中调用- (void)secondVCMethod这个方法,我们知道ViewController和SecondViewController并无继承关系,按照正常的步骤去做程序肯定会因为在ViewController找不到- (void)secondVCMethod这个方法而直接崩溃的


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

//

//  ViewController.m

//  RuntimeTest2

//

//  Created by HenryCheng on 15/12/24.

//  Copyright ?(版权符号) 2015年 www.igancao.com  All rights reserved.

//

#import "ViewController.h"

#import @interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

     [self performSelector:@selector(secondVCMethod)];

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end

运行结果


1

2

3

**2015-12-24 13:54:44.314 RuntimeTest2[3164:835814] -[ViewController secondVCMethod]: unrecognized selector sent to instance 0x7fc3a8535c10**

**2015-12-24 13:54:44.317 RuntimeTest2[3164:835814] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException‘, reason: ‘-[ViewController secondVCMethod]: unrecognized selector sent to instance 0x7fc3a8535c10‘**

***** First throw call stack:**

现在我们来处理一下这个消息,如下


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

//

//  ViewController.m

//  RuntimeTest2

//

//  Created by HenryCheng on 15/12/24.

//  Copyright ?(版权符号) 2015年 www.igancao.com All rights reserved.

//

#import "ViewController.h"

#import @interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

     [self performSelector:@selector(secondVCMethod)];

}

- (id)forwardingTargetForSelector:(SEL)aSelector {

    Class class = NSClassFromString(@"SecondViewController");

    UIViewController *vc = class.new;

    if (aSelector == NSSelectorFromString(@"secondVCMethod")) {

        NSLog(@"secondVC do this !");

        return vc;

    }

    

    return nil;

}

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    return [super resolveInstanceMethod:sel];

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end

运行结果


1

2

**2015-12-24 14:00:34.168 RuntimeTest2[3284:870957] secondVC do this !**

**2015-12-24 14:00:34.169 RuntimeTest2[3284:870957] This is secondVC method !**

我们会发现- (void)secondVCMethod这个方法执行了,程序也并没有崩溃,原因就是在这一步


1

2

3

4

5

6

7

8

9

10

- (id)forwardingTargetForSelector:(SEL)aSelector {

    Class class = NSClassFromString(@"SecondViewController");

    UIViewController *vc = class.new;

    if (aSelector == NSSelectorFromString(@"secondVCMethod")) {

        NSLog(@"secondVC do this !");

        return vc;

    }

    

    return nil;

}

在没有找到- (void)secondVCMethod这个方法的时候,消息继续传递,直到- (id)forwardingTargetForSelector:(SEL)aSelector,然后我在里面创建了一个SecondViewController的对象,并且判断如过有这个方法,就返回SecondViewController的对象。这个函数就是消息的转发,在这儿我们成功的把消息传给了SecondViewController,然后让它来执行,所以就执行了那个方法。同时,也相当于完成了一个多继承!

三、最后一点

当然,还有好几个函数,在上面那张图里面已经清晰的表达了,有兴趣的可以自己试试,看看消息的传递顺序到底是怎么样的。上面提到的这些知识runtime的冰山一角,runtime黑魔法的强大远不止于此,比如方法的调配(Method Swizzling)等,在项目实战中还是很有用的,后面有时间会再介绍.

时间: 2024-08-27 08:28:06

Runtime那些事儿(消息机制)的相关文章

runtime(一):消息机制

会oc不会runtime就是不懂oc.近期项目需要做一些热更新,顺带着复习一下. 首先必须知道的是,oc的消息机制.废话不多说,直接上代码和注释. // // main.m // rumtime // // Created by 123 on 16/7/4. // Copyright © 2016年 yipinbaike. All rights reserved. // #import <Foundation/Foundation.h> #import <objc/message.h&g

Objective-C总Runtime的那点事儿(一)消息机制

Objective-C总Runtime的那点事儿(一)消息机制 RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何二义性.OC的函数调用成为消息发送.属于动态调用过程.在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错.而C语言在编译阶段就会报错).只有在真正运行的时候才

Objective-C总Runtime的那点事儿(一)消息机制【转】

RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何二义性.OC的函数调用成为消息发送.属于动态调用过程.在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错.而C语言在编译阶段就会报错).只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用. 那OC是怎么实现动态调

NSObject头文件解析 / 消息机制 / Runtime解读 (二)

本章接着NSObject头文件解析 / 消息机制 / Runtime解读(一)写 给类添加属性: BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) 其中有一个参数我们再在上一篇中提到过 typedef struct { const char *name;           /**< The na

runtime总结二之消息机制(包括消息转发,消息交换的黑魔法)

runtime的消息机制 前面提到过编译器最终会把我们的消息发送转化为函数调用 消息发送 [object sendMassage] 首先编译器会在运行时将上面的例子转化为objc_msgSend(obj,@selector(sendMassage))这个函数,转换的时候除了方法本身的参数之外,还有两个隐藏的参数一个是id类型的,代表对象的类型,还是一个是SEL类型的,是函数对应的方法的编号,接下来就会按照下面的流程来调用这个方法 通过obj的isa指针找到其所对应的类. 通过SEL先去类的cac

iOS开发runtime学习:一:runtime简介与runtime的消息机制

一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/message.h> #import "Person.h" /* 总结: 1: runtime:必须要导入头文件 <objc/message.h>,此头文件中已经引入了<objc/runtime.h> 任何方法调用本质:发送一个消息,用runtime发送消息.OC底层实现

NSObject头文件解析 / 消息机制 / Runtime解读 (一)

NSObject头文件解析 当我们需要自定义类都会创建一个NSObject子类, 比如: #import <Foundation/Foundation.h> @interface ClassA : NSObject @end 那么NSObject里面具体有什么呢? 我们点到它的头文件里面去看看 @interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; //每个NSObject对象都拥有一个Class类作为成员

RunTime(消息机制) + RunTime(消息转发)

一.消息机制 1.在viewDidLoad中直接用 performSelector:@selector(doSomething) 来调用doSomething方法时,会发现找不到这个方法而奔溃.此时,我们可以在resolveInsantanceMethod:(SEL)see 方法中获取这个所有在运行时阶段的方法,在这个方法中只需要判断一下,将这个方法获取,并且运用Runtime 的 class_addMethod 的方法来将方法和响应函数绑定,进而达到为某一个类添加方法的目的. - (void)

iOS:runtime消息机制

最近在找工作,Objective-C中的Runtime是经常被问到的一个问题,几乎是面试大公司必问的一个问题.当然还有一些其他问题也几乎必问,例 如:RunLoop,Block,内存管理等.其他的问题如果有机会我会在其他文章中介绍.本篇文章主要介绍RunTime. RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何二义性.OC的函数调用成为消息发送