ios runtime 之消息发送机制三次拯救机会
来源:互联网 发布:藤县网络电视 编辑:程序博客网 时间:2024/04/29 18:10
文章目录 一、消息发送概述 二、runtime 三次 拯救机会 1、Method 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 传送门
- ios runtime 之消息发送机制三次拯救机会
- iOS runtime学习之消息转发机制
- 初识 Runtime机制 - 发送消息
- Runtime之消息机制
- Runtime之消息机制
- runtime之消息机制
- iOS Runtime 和消息机制
- iOS Runtime(消息机制)
- iOS Runtime 运行时之三:消息处理机制
- iOS开发之深入探讨runtime机制03-runtime的方法与消息
- iOS runtime 之消息转发
- iOS Objective-C Runtime(-)消息机制
- ios runtime IMP指针 消息转发机制
- iOS Runtime与消息转发机制
- iOS 开发 深入浅出Runtime运行时之官方指南翻译--消息发送
- Objective-C 之 消息机制 (runtime)
- Objective-C 之Runtime消息机制
- runtime objc_msgSend消息发送
- 用Scheme解释器项目来了解Java(三)
- 22. yii 2 sql
- MyEclipse WebSphere开发教程:安装和更新WebSphere 6.1, JAX-WS, EJB 3.0(六)
- 教育类App原型分享-Mimo
- C++笔记 不断更新
- ios runtime 之消息发送机制三次拯救机会
- 为什么有些mp4不能在html5中video标签播放
- SQL2008安装重启失败解决方法
- maven报错问题
- 用Python从零开始创建区块链
- tensorflow实战之 简单卷积神经网络
- Java并发编程:线程池的使用
- Spring MVC之@RequestBody, @ResponseBody 详解
- java程序调用c# webservice