ios runtime 之消息发送机制三次拯救机会

来源:互联网 发布:藤县网络电视 编辑:程序博客网 时间:2024/04/29 18:10
文章目录 一、消息发送概述 二、runtime 三次 拯救机会 1Method  resolution 2、Fast fowarding 3、Normal forwarding

一、消息概述

在编码过程中,我们经常会遇到类似 unrecognized selector 程序 crash的异常。可以总结到如下场景:

1、方法只是声明了,对象直接调用

2、对象 使用 - (id)performSelector:(SEL)aSelector withObject:(id)object , 其中aSelector 没被实现

3、按钮添加点击事件方法,方法没被实现

4、…..

消息在运行时系统的翻译模式如下 :

引入 : #import <objc/message.h>[object sendMessage]  // 在编译时,会被解释成     objc_msgSend(object, @selector(sendMessage));

备注 : 如果上述 代码 在你工程编译不通过的话,可在 Build Settings 将 Enable Strict Checking of objc_msgSend Calls 将其值设置成 NO 即可。

可理解成 向 对象 object 发送 sendMessage 消息。当向object 发送消息时,runtime库会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。

object : 当前消息的接受者
sendMessage : 发送消息内容

但是,在这之前,runtime 会给出 三次 的拯救 机会。查看了一些资料,这三次机会可大概描述成 如下:

1、Method resolution

2、Fast fowarding

3、Normal forwarding

二、runtime 三次 拯救机会

场景 : 定义Person类继承NSObject

// Person.h#import <Foundation/Foundation.h>@interface Person : NSObject@end//Person.m #import "Person.h"@implementation Person@end// main.m文件#import <UIKit/UIKit.h>#import "AppDelegate.h"#import "Person.h"int main(int argc, char * argv[]) {    @autoreleasepool {        Person *jack = [Person new];        [jack performSelector:@selector(speakIMP:) withObject:@"jack"];        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));    }}

运行报错如下:

-[Person speakIMP]: unrecognized selector sent to instance 0x60400001b260*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person speakIMP]: unrecognized selector sent to instance 0x60400001b260'

说明 : Person 及其父类皆未找到 speakIMP 方法未实现

1、Method resolution

objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发。

+(BOOL)resolveInstanceMethod:(SEL)sel{    if (sel == @selector(speakIMP:))     {      // 方式一: 调用下面C 函数     //class_addMethod([self class], sel, (IMP)speakIMP, "b@:@");     //方式二 :调用下面OC 函数  class_getMethodImplementation 改变现有的实现     class_addMethod([self class], sel, class_getMethodImplementation(self,@selector(dynamicAddMethod:)), "b@:@");      return YES;    }    return [super resolveInstanceMethod:sel];}// 实例方法+ (BOOL) resolveClassMethod:(SEL)sel{    return [super resolveClassMethod:sel];}// 类方法// 实现C 函数BOOL speakIMP(id self,SEL _cmd,NSString*name){    NSLog(@"方法添加成功,且接受参数为:%@",name);    return YES;}// oc 函数- (BOOL)dynamicAddMethod:(NSString *name){    if (name != nil)     {         NSLog(@"方法添加成功,且接受参数为:%@",name);        return YES;    }    return NO;}

关于 class_addMethod 参数的说明

1、第一个参数 : Class _Nullable cls。 在哪个类添加方法

2、第二个参数 : SEL _Nonnull name。添加的方法的编号

3、第三个参数 : IMP _Nonnull imp 。方法的实现(其实就是函数指针)。这个C函数,至少包含两个参数 id self 和 SEL _cmd

4、第四个参数 : const char * _Nullable types 。v代表 void , : 代表 SEL

例如 :
”v@:”意思就是这已是一个void类型的方法,没有参数传入。

“i@:”就是说这是一个int类型的方法,没有参数传入。

”i@:@”就是说这是一个int类型的方法,又一个参数传入。

注意 : performSelector是运行时系统负责去找方法的。

假设Person 没有实现 resolveClassMethod 方法,或者 返回NO,那么就会进行消息转发(Fast fowarding)。

.
.
.

2、Fast fowarding(让别的对象去执行这个函数)

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

- (id)forwardingTargetForSelector:(SEL)aSelector{    OtherObject *jack = [[OtherObject alloc] init];    if ([jack respondsToSelector:aSelector])     {         // 相当于[jack speakIMP:@"jack"];           return jack;    }    return [super forwardingTargetForSelector:aSelector];} // 只要不返回 nil 和 self  消息发送过程就会被重启

.
.
.

3、Normal forwarding

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。

(1)如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。

(2)如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{    //查找父类签名    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];    if(methodSignature == nil)    {        methodSignature = [NSMethodSignature signatureWithObjCTypes:"@:@"];    }    return methodSignature;}//如果签名 不为nil ,那么runtime 会创建一个 NSInvocation 对象,并发送forwardInvocation:消息 - (void)forwardInvocation:(NSInvocation *)anInvocation{    SEL sel = anInvocation.selector;    OtherObject *jack = [[OtherObject alloc] init];    if ([jack respondsToSelector:sel])     {        [anInvocation invokeWithTarget:jack];    }}- (void)doesNotRecognizeSelector:(SEL)aSelector{    NSLog(@"程序crash了");}//当方法签名为nil,调用此方法。程序crash。

runtime 消息发送的三次补救机会,可总结入下图所示
这里写图片描述

demo 传送门