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_impbreak跳出,最后返回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,那么得先看x17x17就是__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的走位图,也验证了这个说法,其实也可以解释底层是不分+/-方法的。

但是又暴露出了一个问题,就是每个类都得写一次resolveInstanceMethodresolveClassMethod,那要是有多个类呢?都写一遍就太麻烦了。那么给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(四): 动态方法解析相关推荐

  1. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  2. Runtime底层原理--动态方法解析、消息转发源码分析

    了解了Runtime函数含义,我们就可以直接使用Runtime的API了,那接下来继续探究Runtime的源码,经过源码分析来更加深刻的了解Runtime原理. 开发应用 都知道Runtime很重要, ...

  3. Runtime底层原理--动态方法解析总结

    方法的底层会编译成消息,消息进行递归,先从实例方法开始查找,到父类最后到NSObject.如果在汇编部分快速查找没有找到IMP,就会进入C/C++中的动态方法解析进入lookUpImpOrForwar ...

  4. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  5. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  6. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  7. python求两个数的最大公约数穷举法_C++求最大公约数四种方法解析

    C++求最大公约数的四种方法思路,供大家参考,具体内容如下 将最近学的求最大公约数的四种方法总结如下: 第一种:穷举法之一 解释:拿其中一个数出来,用一个临时变量(tem)保存,每次都把那两个数除以这 ...

  8. 使用RunTime添加动态方法、方法交换、获取所有属性来重写归档解档

    #import "JZGMMMMModel.h" #import <objc/message.h> @implementation JZGMMMMModel //**动 ...

  9. IOS中Json解析的四种方法

    2019独角兽企业重金招聘Python工程师标准>>> 作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有的json代码格式比较混乱,可以使用此& ...

  10. 【转】IOS中Json解析的四种方法

    原文网址:http://blog.csdn.net/enuola/article/details/7903632 作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式. 有 ...

最新文章

  1. 开发流媒体服务器_Github选出10大开源免费的RTSP流媒体项目
  2. PL/SQL如何设置 窗口列表默认显示
  3. AXI_03 AXI_LITE_SLAVE_IP核设计与验证
  4. Linux安装net的工具,centos7安装netbox(不错开源网络管理工具) – 运维那些事
  5. 硬件:RS232基础知识笔记
  6. linux基础知识个人总结
  7. JavaScript操作XML(IE6下)
  8. php 点击按钮自动复制,实现点击元素自动复制内容的功能
  9. synchronized 线程同步,添加对象锁与类锁
  10. bigemap功能介绍,视频教程
  11. 误差函数erf matlab,matlab求解erf误差函数方法(有详细注释 )
  12. 深度学习入门(看了就会)
  13. 基于第三代测序技术的基因组组装方法及其在烟草中的应用
  14. 面向95后的营销和增长,你需要知道这些( ゜- ゜)つロ 乾杯
  15. 深度学习FPGA实现基础知识17(图像处理卷积运算 矩阵卷积)
  16. 关于STM32串口3的使用,接收并解析一帧数据
  17. k30最小宽度380不管用了_各场所疏散楼梯净宽度知识点归纳
  18. Java高性能系列-(一)VM生命周期
  19. ITK 形态学处理(Morph process)
  20. 运营商物联网卡ICCID号介绍

热门文章

  1. Linux常用终端命令及扩展(五)
  2. Struts2中4个核心组件_笔记
  3. 诗意的边缘(PHP顶级框架Zend Fr
  4. 实例6 函数的返回值
  5. MySQL 字符集和校对
  6. 「专题训练」k-Tree(CodeForces Round #247 Div.2 C)
  7. Dubbo--002--例子程序
  8. nginx 服务器并发优化
  9. Kotlin基础-对象声明和表达式
  10. C++结构体字节对齐