RunTime之二:消息传递机制

2020-11-06  本文已影响0人  双鱼子曰1987

提醒,阅读本章,需要首先了解上一章节的基础知识。

一、「消息传递」的疑问

面向对象拥有强大的「数据封装」能力,而「消息传递」更是其强大利器之一,那么RunTime是如何实现的呢?

所谓的「消息传递」指的就是「方法的调用」,其中「方法的调用」又可以分为「实例方法」调用 和 「类方法」调用。

其中,「方法的调用」可以分为两个步骤来看,一个根据「方法名」找到「实现」;一个是执行方法的「实现」代码。

例如,定义一个Person类,继承NSObject。
@interface Person : NSObject
- (void)sayHelloWorld;
@end
@implementation ZTwinkleLoadView
- (void)sayHelloWorld {
     NSLog(@"HelloWorld!");
}
@end

// 调用实例方法
Person *obj = [[Person alloc] init];
[obj sayHelloWorld]

// 调用类方法
[Person hash] 

二、「方法」的「查找」过程

「方法调用」的本质:「消息接收者」 找到对应的「方法」SEL 的 「实现」IMP

这其实就是一个寻找搜索的过程,而在链表寻找,往往都是不那么快,这也是结构体struct objc_class 需要cache的原因。

2.1、「实例方法」的「查找」过程

方法的查找过程
2.2、「类方法」的「查找」过程

其查找的过程 和 「实例方法」的「查找」过程 的过程类似,不一样的是寻找的是沿着「元类」的路径。

其大致的过程如下:

2.3、解答疑问:

2.4、若干问题

OC中类的继承链,也是通过super_class实现的。由于objc_class只有一个superClass指针,因此它也只能支持单继承。

可以推断,如果多继承,那么这个链路就会显得太过于复杂;另一方面,多继承在实际开发中并不常见,而且推荐少用或者不用,或者可以通过其他方式(例如接口)实现设计意图,因此设计者舍弃。

2.5、一张图说明「实例方法」和「类方法」的查找过程

对象和类的isa指针的指向关系
注意图中,实线表示superClass的查找过程,虚线表示isa指针的查找过程。

三、「方法」的「执行」过程,即objc_msgSend

所有 Objective-C 方法调用在编译时都会转化为对 C 函数 objc_msgSend 的调用。

objc_msgSend函数的定义:id objc_msgSend(id receiver, SEL op, ...),前面两个参数固定,第一个参数是接收者id,第二个参数方法名SEL,后面的...可变参数列表,传递函数的参数。

objc_msgSend函数的定义和 IMP = id + SEL + ...(即参数列表) 的定义是一模一样的。因此,查找到方法的具体「实现」,找到是IMP执行,具体执行代码的是objc_msgSend函数。

至于objc_msgSend函数的内部代码实现,有兴趣的同学可以深入学习下。深入解构objc_msgSend函数的实现

四、方法找不到的异常处理

现实开发中,经常会遇到一种Crash,就是“unrecognized selector sent to instance ,意思是某个实例或者类找不到某个方法名”,这背后的处理机制如何呢?有没有避免Crash的统一处理机制呢?带着问题,继续往下看。

从上面关于「实例方法」和「类方法」的查找过程中,它们最后都有个异常处理逻辑,就是当方法找不到的时候进入「消息异常捕获逻辑」或者「消息的转发机制」。

4.1、「消息的转发机制」的原理图

这个「消息异常捕获逻辑」内部机制又是怎么样的呢?
OC设计一套异常的处理机制,即「消息的转发机制」,当某个方法名找不到对应的「实现」的时候,给你个机会处理这种异常。这种能力就为我们提供 “方法找不到Crash” 的统一拦截处理方案。

4.2、「消息的转发机制」可以分为三个步骤:

// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(xxx_unrecognized_func)) { 
        class_addMethod([self class], sel, (IMP) xxx_unrecognized_func, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void xxx_unrecognized_func(id obj, SEL _cmd) {
    ...
}

上面的例子中,class_addMethod 的特殊参数 v@: 称为 Type Encodings ,其具体说请跳转 传送门

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO; 

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(xxx_unrecognized_func)) {
        return [[xxx_Class alloc] init];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"xxx_unrecognized_func"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;
    
    xxx_Class *obj = [[xxx_Class alloc] init];
    if([obj respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:obj];
    } else {
        [self doesNotRecognizeSelector:sel];
    }
}

4.3、注意

异常解析的方法都被定义在NSObject根类中,由于OC所有的类都是继承来自NSObject,因此所有的类或者类的实例都可以处理异常消息。

4.4、利用NSInvocation实现动态方法调用

即随意指定某个对象,执行某个方法。如下

NSMethodSignature *sign = [UIView.class instanceMethodSignatureForSelector:@selector(setBackgroundColor:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign];

[invocation invokeWithTarget:viewObj_1];
[invocation invokeWithTarget:viewObj_2];
[invocation invokeWithTarget:viewObj_3];

其他

RunTime原理之一:如何让Objective-C支持面向对象的能力呢?
Runtime:消息发送机制、动态方法解析、消息转发机制
iOS 开发:『Runtime』详解(一)基础知识

上一篇 下一篇

猜你喜欢

热点阅读