Objective-C的动态性是由Runtime API来支撑的,Runtime API提供的接口基本都是C语言的,源代码由C\C++\汇编语言编写。
isa详解
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构isa_t,还使用位域来存储更多的信息。
1 | //arm64架构下,isa_t的结构参考如下: |
字段含义如下:
- nonpointer: 0代表普通的指针,存储着Class、Meta-Class对象的内存地址; 1代表优化过,使用位域存储更多的信息;
- has_assoc: 是否有设置过关联对象,如果没有,释放时会更快;
- has_cxx_dtor: 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快;
- shiftcls: 存储着Class、Meta-Class对象的内存地址信息;
- magic: 用于在调试时分辨对象是否未完成初始化;
- weakly_referenced: 是否有被弱引用指向过,如果没有,释放时会更快;
- deallocating: 对象是否正在释放;
- extra_rc: 里面存储的值是引用计数器减1;
- has_sidetable_rc: 引用计数器是否过大无法存储在isa中,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中;
arm64架构下取出shiftcls的掩码ISA_MASK为0x0000000ffffffff8ULL,由此可见,class对象和meta-class对象的地址值最后3位都是0.
Class结构
Class本质上为一个结构体类型:
1 | typedef struct objc_class *Class; |
与该结构体相关的主要定义如下:
1 | struct objc_object { |
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。
method_t是对方法、函数的封装:
1 | struct method_t { |
IMP代表函数的具体实现:
1 | typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); |
SEL代表方法或函数名,一般叫做选择器,底层结构跟char *类似:
- 可以通过@selector()和sel_registerName()获得;
- 可以通过sel_getName()和NSStringFromSelector()转成字符串;
- 不同类中相同名字的方法,所对应的方法选择器是相同的;
1 | typedef struct objc_selector *SEL; |
types包含了函数返回值、参数编码的字符串。 相关介绍可以参考:Type Encodings 。
方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。
1 | struct cache_t { |
方法调用
OC中的方法调用,其实都是转换为objc_msgSend函数的调用。objc_msgSend的执行流程可以分为3大阶段:
- 消息发送
- 动态方法解析
- 消息转发
objc_msgSend源码导读
消息发送
从源码归纳出如下流程:
动态方法解析
假如在消息发送过程中,没有查找到方法,那么就会进入动态方法解析。动态方法解析就是在运行时临时添加一个方法实现,来进行消息的处理。
开发者可以实现以下方法,来动态添加方法实现:
- resolveInstanceMethod:
- resolveClassMethod:
下面是代码示例:
1 | void notFound_eat(id self, SEL _cmd) |
下面是class_addMethod添加的另一种方式:
1 | - (void)notFound_eat |
动态解析过后,会重新进入到“消息发送”的流程,“从receiverClass的cache中查找方法”这一步开始执行。
在动态方法解析完成后,会将标识tridResolver设置为YES,表示已经进行过动态解析,避免消息发送和动态方法解析之间出现死循环。
动态方法解析最佳的一个实践用例就是@dynamic的实现。
消息转发
下面是消息转发阶段的流程图:
super
super调用底层会转换为objc_msgSendSuper2()函数调用, 相关定义及注释如下:
1 | /** |
使用super调用时,消息的接收者仍然是self,只是会从父类中开始寻找方法。
Runtime API
类
1 | 动态创建一个类(参数:父类,类名,额外的内存空间) |
成员变量
1 | 获取一个实例变量信息 |
属性
1 | 获取一个属性 |
方法
1 | 获得一个实例方法、类方法 |