1、oc总是尽可能把更多的事情从编译时期和链接时期,推迟到runtime(运行时期)来动态执行。这就意味着oc不仅需要编译器,还需要一套runtime system来支撑它的运行。
2、oc程序通过以下3种方式跟runtime system进行交互:
1)通过oc源代码
2)通过定义在Foundation framework中的NSObject类的方法
3)直接调用runtime函数
3、
编译器会为每个类构建一个结构体,源码如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // oc中,类也是一种对象,类对象的isa指针指向元类
if !OBJC2
Class super_class OBJC2_UNAVAILABLE; // 指向父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法列表,表项包含方法对应的selector以及地址
struct objc_cache *cache OBJC2_UNAVAILABLE; // 缓存调用过的方法,加速方法查找
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议列表
endif
} OBJC2_UNAVAILABLE;
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class;
对象是类的实例,源码如下:
/// Represents an instance of a class.struct objc_object { Class isa OBJC_ISA_AVAILABILITY; // 指向类(对象),通过isa指针,对象可以访问它的类,进而访问到它的类的父类};
编译器把如下消息表达式:
[receiver message]
转成messaging function(消息发送函数),即 objc_msgSend:
objc_msgSend(receiver, selector)
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息表达式直到runtime才和方法的实现绑定在一起。
objc_msgSend函数负责搞定动态绑定需要的一切:
1)查找selector对应的过程(方法的实现)。对于不同类实现的相同方法,根据receiver对应的类来精确区分定位。
2)然后调用过程,把receiver的指针以及相关的参数传给过程。
3)最后,把过程的返回值拿到当成自己的返回值。
动态绑定示意图如下:
当一个消息发送给一个对象时(实例对象或者类对象),objc_msgSend函数会根据isa指针去对象的类的方法列表里查找selector(selector可以看做方法的key);如果找不到,就顺着类的superclass去父类的方法列表里查找,以此类推,直到NSObject类(还找不到就会触发消息转发逻辑)。一旦找到,objc_msgSend函数就把接收消息的对象的结构体传给selector对应的方法。为了加快查找速度,runtime会缓存调用过的方法。
熟悉c++的同学知道,上述查找方法的流程在c++编译时也是这么干的,不同的是oc在运行时这么干而已,也因此oc被称为动态语言。
消息转发和基于method swizzling的hook方案,后续补上。
官方文档链接:
https://developer.apple.com/reference/objectivec/objective_c_runtime?language=objc
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048?language=objc