本文仅供自我学习笔记。参考《小码哥的runtime》教学。
终于有时间整理了,想整理的几个知识点都与runtime有关干脆就一起整理了。
Objective-C是一门动态性比较强的编程语言,它的其动态性是由RuntimeAPI来支撑的。
RuntimeAPI提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
接下来我们来了解我们的第一个知识点isa指针。Objective-C语言里面每个对象都会有一个隐藏数据结构,这个数据结构同时是OC对象的第一个成员变量。其实这个数据结构就是我们的isa指针。isa指针通常用来指向对象所属的类,从而通过调用方法时来通过isa指针找到想要的方法和属性。(元类的isa指针指向的就是元类)在arm64架构之前,isa就是一个普通的指针,主要储存Class,Meta-Class对象的内存地址。在arm64架构之后,对isa进行了优化,变成看一个共用体结构,还使用位域来储存更多的信息。详情我们看下面这个结构图。
首先我们看isa里面的struct,详情见下图我直接在图上写注释方便大家看。
看完struct我们接下来看Class cls。
看到这里我们需要注意的是bits。这个返回的是一个class_rw_t。所以此时我们从class_rw_t开始看起这里我同样用一张图来表示。
再接下来我们看一下const class_ro_t *ro 这个
接下来我们看method_t
接下来我们在看cache_t(也就是方法缓存)
第一个知识点isa指针到此结束。
接下来我们看第二个知识点:objc_msgSend执行流程。
OC中方法的调用,其实都是转为objc_msgSend函数的调用。
objc_msgSend函数执行流程可以分为三大阶段。
01.消息发送。
02.动态方法解析。
03.消息转发
我们首先看第一个阶段也就是消息发送
接下来我用文字来描述一下流程就不弄图了,弄图的话麻烦。
首先系统判定消息的接受者是否存在,如果不存在第一阶段失败就要下面的流程。
如果消息的接受者存在,则系统从接受者的方法缓存中去找,找到了方法则发送消息(消息发送成功)。如果失败需要从接受者的方法列表里面去找,找到了方法调用成功(消息发送成功),找不到则需要去父类的方法缓存里面去找,如果找到了方法调用成功(消息发送成功)。如果没有找到就会找当前父类还有没有父类如果没有则消息发送失败我们进入第二阶段。
在方法列表中查找方法时,已经排序的采用的二分查找法,如果没有排序的,采用的遍历查找。
接受者通过isa指针找到接受者这个类。接受类通过superClass指针找到superClass的类。
当第一个阶段失败了,这里我们就需要进行第二阶段的处理了。
也就是我们的动态方法解析。系统第一步会判定这个方法是否曾经解析过。如果解析过重新走
消息发送流程。我们可以通过以下的方法来添加动态方法实现。
+resolveInstanceMethod:和+resolveClassMethod:一个是类方法的动态添加方法实现,一个是实例化方法添加动态方法实现。每次动态解析过后都会重走第一阶段。
接下来我们看第三阶段也就是消息转发。
系统会检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法。若该方法返回值对象非nil或非self,则向该返回对象重新发送消息。 这个叫做快速转发。如果失败我们会继续会下走也就是标准的消息转发。
runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。我们可以在forwardInvocation里面自定义任何逻辑。
接下来我们看看我们的第三个知识点,也就是super的本质。
super的调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数struct objc_super2和SEL
接下来我们看最后一个知识点llvm的中间代码(IR)
oc代码在变成机器代码之前,会被llvm编译器转化为中间代码。我们是可以用以下命令直接生成llvm的中间代码的。
clang -emit -llvm -S main.m
语法简介
@ - 全局变量
% - 局部变量
alloca - 在当前执行的函数堆栈帧中分配内容,该当函数返回到其调用者时,将自动释放内存
i32 - 32位4字节的整数
align - 对齐
load - 读出 store写入
icmp - 两个整数值的比较,返回布尔值。
br - 选择分支。根据条件来转向label,不根据条件跳转的话类似 goto
label - 代码标签
call - 调用函数