iOS底层探索之Runtime(四): 动态方法解析
1. 回顾
iOS底层探索之Runtime(一):运行时&方法的本质
iOS底层探索之Runtime(二): objc_msgSend&汇编快速查找分析
iOS底层探索之Runtime(三): lookUpImpOrForward慢速查找分析
在上一篇博文中,介绍了Runtime
的慢速查找流程lookUpImpOrForward
,本章内容主要分析动态方法解析
流程。
在缓存中、自己的
class_rw_t
中、父类的cache
中、父类的class_rw_t
中都没有找到imp
,就会进入objc_msgSend
的第二个阶段动态方法解析
。
2. 动态方法解析
2.1 resolveMethod_locked
从源码中可以发现,在lookUpImpOrForward
里面循环遍历没有找到imp
之后就会进入下面这个判断,注释也写的很明显了。
// No implementation found. Try method resolver once.
//behavior = 3 , LOOKUP_RESOLVER = 2
// 3 & 2 = 2,就是找两个的相同值
//0011
//0010
//0010 -> 2 if (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER;//异或:相同为0,不同为1// 3 ^= 2 -> 1 ,behavior=1,下次再进来就是behavior & LOOKUP_RESOLVER -> 相当于1 & 2=0,为0这个方法也就只执行一次,相当于单例的作用return resolveMethod_locked(inst, sel, cls, behavior);}
- resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{runtimeLock.assertLocked();ASSERT(cls->isRealized());runtimeLock.unlock();if (! cls->isMetaClass()) {// try [cls resolveInstanceMethod:sel]resolveInstanceMethod(inst, sel, cls);} else {// try [nonMetaClass resolveClassMethod:sel]// and [cls resolveInstanceMethod:sel]resolveClassMethod(inst, sel, cls);if (!lookUpImpOrNilTryCache(inst, sel, cls)) {resolveInstanceMethod(inst, sel, cls);}}// chances are that calling the resolver have populated the cache// so attempt using itreturn lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
从resolveMethod_locked
方法里面可以看出,主要是判断是不是元类,非元类和元类两种情况,分情况进行处理。
- 非元类:调用
resolveInstanceMethod:
- 元类:调用
resolveClassMethod:
在正式开始分析之前,我们先看看下面这个例子
在JPStudent
的父类JPPerson
类里面,声明了一个方法jp_sayHello
,但是没有实现,那么JPStudent
子类调用了父类的方法,会出现什么情况呢?
JPPerson *jp = [[JPPerson alloc]init];[jp jp_sayHello]
很显然程序是会奔溃的,奔溃信息如下:
[9441:373947] -[JPStudent jp_sayHello]: unrecognized selector sent to instance 0x100709660
[9441:373947] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[JPStudent jp_sayHello]: unrecognized selector sent to instance 0x100709660'
那么为什么会报这个unrecognized selector sent to instance
经典的错误呢?我们从源码来分析
在lookUpImpOrForward
方法里面,如果所有的父类里面没有找到imp
,就会给imp
赋值forward_imp
,break
跳出,最后返回imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;IMP imp = nil;.....代码省略......if (slowpath((curClass = curClass->getSuperclass()) == nil)) {// No implementation found, and method resolver didn't help.// Use forwarding.imp = forward_imp;break;}.....代码省略......return imp;
2.2 _objc_msgForward_impcache
- _objc_msgForward_impcache
那么赋值的这个imp
,也就是_objc_msgForward_impcache
,到底是个什么imp
呢?
在源码里面全局搜索objc_msgForward_impcache
发现
objc_msgForward_impcache
只有一行代码
然后再继续搜索__objc_msgForward
__objc_msgForward
里面又调用了TailCallFunctionPointer
然后再搜索TailCallFunctionPointer
.macro TailCallFunctionPointer// $0 = function pointer valuebraaz $0
.endmacro
意思是要跳转
$0
,那么得先看x17
,x17
就是__objc_forward_handler
那么再全局搜索
__objc_forward_handler
,发现没有找到,那么再去掉下划线搜索
我的天哪!原来报错信息是在这里打印的啊!这一波操作
666
啊!
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p ""(no message forward handler is installed)", class_isMetaClass(object_getClass(self)) ? '+' : '-', object_getClassName(self), sel_getName(sel), self);
2.3 知识点补充
OC
底层是不分实例方法(-
)和对象方法(+
)的,底层会自动给你加上-
号和+
号,然后按格式打印报错信息。还记得isa
走位图吗?iOS底层探索之类的结构(上):ISA
从图中,可以知道,根元类的superClass
指向了根类,我相信大家都有疑问?
- 类方法,存在元类里面,实例方法存在类里面。
- 当调用类方法,会沿着继承链,往上找,一直找到元类都没有找到类方法,就回会去根类里面找,根类里面发现有个同名的实例方法,就会直接调用。
- 如果根类里面没有同名的实例方法就会报错
unrecognized selector sent to instance
这就解释了OC
其实是不分+
方法和-
方法的,对于底层来说,都是消息发送,sel
都一样。所以会有根元类的superClass
指向了根类这么一个指针的指向。
那么如果方法没有实现,就只能程序崩溃,报错了吗?有没有什么补救措施,好让我拯救地球呢!
有的靓仔,苹果工程师会满足你的!请继续往下看!
2.4 resolveInstanceMethod
在JPPerson
类的.m
文件下,实现resolveInstanceMethod
方法
程序运行起来报错了,但是我们发现,在报错奔溃之前
走了resolveInstanceMethod
方法,那么也就是说,我们可以提前处理,不让程序奔溃。
@implementation JPPerson- (void)jp_sayNB {NSLog(@"%@,%s",self,__func__);
}+(BOOL)resolveInstanceMethod:(SEL)sel {// 方法匹配NSString *methodName = NSStringFromSelector(sel);if ([methodName isEqualToString:@"jp_sayHello"]) {IMP sayNBImp = class_getMethodImplementation(self, @selector(jp_sayNB));Method method = class_getClassMethod(self, @selector(jp_sayNB));const char *type = method_getTypeEncoding(method);return class_addMethod(self, sel, sayNBImp, type);//添加方法}return [super resolveInstanceMethod:sel];
}
程序跑起来,看看结果如何
程序并没有奔溃,而是走了我们添加的方法里面去,这就是对象方法的动态解析。
2.5 resolveClassMethod
那么我们再来看看,类方法的动态解析。
从图中可以看出,在调用了resolveClassMethod
方法,也调用了resolveInstanceMethod
方法,这是怎么回事呢?
从底层代码来看,处理流程差不多,那么我们现在去实现类方法的动态解析。
Method class_getClassMethod(Class cls, SEL sel)
{if (!cls || !sel) return nil;return class_getInstanceMethod(cls->getMeta(), sel);
}
class_getClassMethod
方法,是获取元类的实例方法。在类里面实现resolveClassMethod
,相当于元类里面以对象方式存在
JPPerson
类调用没有实现方法[JPPerson jp_sayHappy]
,在JPPerson
中实现resolveClassMethod
方法解析动态解析
+ (void)jp_say666 {NSLog(@"%@,%s",self,__func__);
}// 相当于 元类中的对象方法
+(BOOL)resolveClassMethod:(SEL)sel {//获取元类的对象方法NSString *methodName = NSStringFromSelector(sel);if ([methodName isEqualToString:@"jp_sayHappy"]) {IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("JPPerson"), @selector(jp_say666));Method method = class_getClassMethod(objc_getMetaClass("JPPerson"), @selector(jp_say666));const char *type = method_getTypeEncoding(method);return class_addMethod(objc_getMetaClass("JPPerson"), sel, sayNBImp, type);//添加方法}return [super resolveClassMethod:sel];
}
动态方法解析,么得问题,
jp_sayHappy
方法并没有实现,但是在动态方法解析的时候,可以添加jp_say666
方法来防止崩溃。上面提到了,resolveInstanceMethod
方法还走了一次,这是因为类方法,存在元类里面,但是同时也可以以对象方法的形式存在,就有两条路径,上面isa
的走位图,也验证了这个说法,其实也可以解释底层是不分+/-
方法的。
但是又暴露出了一个问题,就是每个类都得写一次resolveInstanceMethod
和resolveClassMethod
,那要是有多个类呢?都写一遍就太麻烦了。那么给NSObject
写一个分类NSObject+JPResolver
就可以很好的解决这种问题。
+(BOOL)resolveInstanceMethod:(SEL)sel {// 方法匹配NSString *methodName = NSStringFromSelector(sel);if ([methodName isEqualToString:@"jp_sayHello"]) {IMP sayNBImp = class_getMethodImplementation(self, @selector(jp_sayNB));Method method = class_getClassMethod(self, @selector(jp_sayNB));const char *type = method_getTypeEncoding(method);return class_addMethod(self, sel, sayNBImp, type);//添加方法}else if ([methodName isEqualToString:@"jp_sayHappy"]) {IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("JPPerson"), @selector(jp_say666));Method method = class_getClassMethod(objc_getMetaClass("JPPerson"), @selector(jp_say666));const char *type = method_getTypeEncoding(method);return class_addMethod(objc_getMetaClass("JPPerson"), sel, sayNBImp, type);//添加方法}return NO;
}
那要是上面两种情况,程序员都不处理呢?
苹果:给你机会,你得珍惜啊!你不处理,我也不能崩溃啊!那就再来一次吧!
那么再来一次是什么机会呢?请听下回分解
iOS底层探索之Runtime(五): 消息转发
3.总结
- 在
lookUpImpOrForward
慢速查找imp
没有找到会进入动态方法解析流程 resolveInstanceMethod
实例方法解析,可以动态添加方法,防止崩溃。resolveClassMethod
类方法解析,也会调用resolveInstanceMethod
,根据isa
走位图可以知道,类方法是存在元类里面,但是也会以对象方法形式存在在类中,主要是因为底层不区分+
和-
。
更多内容持续更新
iOS底层探索之Runtime(四): 动态方法解析相关推荐
- iOS底层探索二(OC 中 alloc 方法 初探)
前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...
- Runtime底层原理--动态方法解析、消息转发源码分析
了解了Runtime函数含义,我们就可以直接使用Runtime的API了,那接下来继续探究Runtime的源码,经过源码分析来更加深刻的了解Runtime原理. 开发应用 都知道Runtime很重要, ...
- Runtime底层原理--动态方法解析总结
方法的底层会编译成消息,消息进行递归,先从实例方法开始查找,到父类最后到NSObject.如果在汇编部分快速查找没有找到IMP,就会进入C/C++中的动态方法解析进入lookUpImpOrForwar ...
- iOS 底层探索 - 消息转发
一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...
- iOS底层探索(二) - 写给小白看的Clang编译过程原理
iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...
- iOS 底层探索篇 —— KVC 底层原理
iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...
- python求两个数的最大公约数穷举法_C++求最大公约数四种方法解析
C++求最大公约数的四种方法思路,供大家参考,具体内容如下 将最近学的求最大公约数的四种方法总结如下: 第一种:穷举法之一 解释:拿其中一个数出来,用一个临时变量(tem)保存,每次都把那两个数除以这 ...
- 使用RunTime添加动态方法、方法交换、获取所有属性来重写归档解档
#import "JZGMMMMModel.h" #import <objc/message.h> @implementation JZGMMMMModel //**动 ...
- IOS中Json解析的四种方法
2019独角兽企业重金招聘Python工程师标准>>> 作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有的json代码格式比较混乱,可以使用此& ...
- 【转】IOS中Json解析的四种方法
原文网址:http://blog.csdn.net/enuola/article/details/7903632 作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有 ...
最新文章
- 开发流媒体服务器_Github选出10大开源免费的RTSP流媒体项目
- PL/SQL如何设置 窗口列表默认显示
- AXI_03 AXI_LITE_SLAVE_IP核设计与验证
- Linux安装net的工具,centos7安装netbox(不错开源网络管理工具) – 运维那些事
- 硬件:RS232基础知识笔记
- linux基础知识个人总结
- JavaScript操作XML(IE6下)
- php 点击按钮自动复制,实现点击元素自动复制内容的功能
- synchronized 线程同步,添加对象锁与类锁
- bigemap功能介绍,视频教程
- 误差函数erf matlab,matlab求解erf误差函数方法(有详细注释 )
- 深度学习入门(看了就会)
- 基于第三代测序技术的基因组组装方法及其在烟草中的应用
- 面向95后的营销和增长,你需要知道这些( ゜- ゜)つロ 乾杯
- 深度学习FPGA实现基础知识17(图像处理卷积运算 矩阵卷积)
- 关于STM32串口3的使用,接收并解析一帧数据
- k30最小宽度380不管用了_各场所疏散楼梯净宽度知识点归纳
- Java高性能系列-(一)VM生命周期
- ITK 形态学处理(Morph process)
- 运营商物联网卡ICCID号介绍