前面我们已经讲解 Runtime 的基本概念和基本使用, 如果大家对 Runtime 机制不是很了解, 可以先看一下以前的博客, 会对理解这篇博客有所帮助!!!
Runtime 基本概念: https://www.cnblogs.com/guohai-stronger/p/9154889.html
Runtime 基本使用: https://www.cnblogs.com/guohai-stronger/p/9184356.html
看过上面两篇博客之后, 大家可能会对 Runtime 有所了解, 但在上述讲述 Runtime 还有一个问题, 当对象无法接收到相关的消息时候, 又会发生什么? 这就是我们即将讲的消息转发机制.
一, 前言
在讲述消息转发机制前, 先通过一个简单的例子
- id num = @123;
- // 输出 123
- NSLog(@"%@", num);
- // 程序崩溃, 报错 [__NSCFNumber appendString:]: unrecognized selector sent to instance 0x7b27
- [num appendString:@"Hello World"];
在程序代码中加入上面代码, 运行过程为: 先在相关的类中, 搜索方法列表, 如果出现找不到则会一直向上搜索到根部 (也就是 NSObject); 如果出现还找不到并且消息转发都失败, 此时就会执行 doesNotRecognizeSelector: 紧接着方法报 unrecognized selector 错误?
下面我们来讲述消息转发的过程中, 为什么经常出现消息的最后三次机会?
第一步: 所属类的动态方法解析
首先, 如果沿着继承树没有搜索到相关的方法时, 则就会向接受者所属的类进行一次请求, 看是否可以动态的添加一个方法, 如下:
+(BOOL)resolveInstanceMethod:(SEL)name
注意这是一个类方法, 因为向接受者所属的类进行一次请求.
下面我们举个例子:
- #import "ViewController.h"
- #import <objc/runtime.h>
- @interface ViewController ()
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.title = @"Runtime";
- [self performSelector:@selector(dynamicSelector) withObject:nil];
- }
- + (BOOL)resolveInstanceMethod:(SEL)sel{
- if (sel == @selector(dynamicSelector)) {
- class_addMethod([self class],sel, (IMP)myMehtod,"v@:");
- return YES;
- }else{
- return [super resolveInstanceMethod:sel];
- }
- }
- void myMehtod(id self,SEL _cmd){
- NSLog(@"This is added dynamic");
- }
在看结果之前, 我们先简单了解下: class_addMethod(上面出现的)
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
此函数共有四个参数:
Class cls: 要添加的类对象
name: 添加后的 selector 方法名字
imp: 实现方法
type: 方法参数的编码
方法的返回值为 BOOL 类型: YES 表示本类可以处理; NO 代表需要使用转发机制.
下面是上面的运行结果, 结果如下:
如果第一步不能处理, 会进行到第二步.
第二步: 调用 forwardingTargetForSelector 把任务转发给另一个对象.
- #import "ViewController.h"
- #import <objc/runtime.h>
- @interface CustomObject : NSObject
- -(void)dynamicSelector;
- @end
- @implementation CustomObject
- -(void)dynamicSelector{
- NSLog(@"hello world");
- }
- @end
- @interface ViewController ()
- @property (strong,nonatomic)CustomObject * myObj;
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.myObj = [[CustomObject alloc] init];
- [self performSelector:@selector(dynamicSelector) withObject:nil];
- }
- -(id)forwardingTargetForSelector:(SEL)aSelector{
- if (aSelector == @selector(dynamicSelector) && [self.myObj respondsToSelector:@selector(dynamicSelector)]) {
- return self.myObj;
- }else{
- return [super forwardingTargetForSelector:aSelector];
- }
- }
- @end
如果第二步骤也不能处理的时候, 会交给第三步骤.
第三步: 调用 forwardInvocation 转发给其他
- #import "ViewController.h"
- #import <objc/runtime.h>
- @interface CustomObject : NSObject
- -(void)dynamicSelector;
- @end
- @implementation CustomObject
- -(void)dynamicSelector{
- NSLog(@"hello world");
- }
- @end
- @interface ViewController ()
- @property (strong,nonatomic)CustomObject * myObj;
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- self.myObj = [[CustomObject alloc] init];
- [self performSelector:@selector(dynamicSelector) withObject:nil];
- }
- -(void)forwardInvocation:(NSInvocation *)anInvocation{
- if ([self.myObj respondsToSelector:[anInvocation selector]]) {
- [anInvocation invokeWithTarget:self.myObj];
- }else{
- [super forwardInvocation:anInvocation];
- }
- }
- - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
- return [CustomObject instanceMethodSignatureForSelector:aSelector];
- }
- @end
上面就是 iOS Runtime 消息的转发机制, 欢迎指正!!!
来源: https://www.cnblogs.com/guohai-stronger/p/9408545.html