三大流程
在 OC 中给一个对象发送消息时,比如下面的代码:
NSObject *obj = [NSObject new];
编译成 C++ 代码可以看到,底层调用的都是objc_msgSend
:
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
objc_msgSend 总共有三大步骤,分别为:消息发送、动态解析、消息转发。在梳理这三步之前,有必要先讲一下 isa 和 class 的底层结构。
isa 和 class 底层结构
isa
在 arm64 架构之前,isa 就是一个指向类对象的指针,到了 arm64,它变成了共同体,承载了更多的责任。
以下是 objc4-818.2
里的 isa_t 信息:
- nonpointer
- 值为 0,代表普通的指针,存储着类对象、元类对象的内存地址。
- 值为 1,代表已优化,使用位域存储更多的信息。
- has_assoc:是否设置关联对象,若没有则对象释放会更快。
- has_cxx_dtor:是否有 C++ 析构函数,若没有则对象释放会更快。
- shiftcls:存放类对象、元类对象的内存地址。
- magic:在调试时判断对象是否完成初始化。
- weakly_referenced:是否有被弱引用指向过,若没有则对象释放会更快。
- unused:未使用。
- has_sidetable_rc:
- 若值为 0,则引用计数可以存储在 extra_rc 中。
- 若值为 1,则代表引用计数过大,需存储在 SideTable 的类的属性中。
- extra_rc:存储的值为对象的引用计数减一。
Class
- objc_class:类对象的底层结构。
struct objc_class : objc_object {
......
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
......
}
- cache_t:散列表实现的方法缓存。
struct cache_t {
......
unsigned capacity() const; // 散列表容量
struct bucket_t *buckets() const; //散列表
Class cls() const; // 类对象或元类对象
mask_t occupied() const; //已占用的数量
......
}
- class_data_bits_t
struct class_data_bits_t {
......
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
......
};
- class_rw_t:存放类对象的方法、属性、协议列表,可读可写。
struct class_rw_t {
explicit_atomic<uintptr_t> ro_or_rw_ext;
const method_array_t methods() const { ... }
const property_array_t properties() const { ... }
const protocol_array_t protocols() const { ... }
};
了解了 isa 和 class 的底层结构,下面来详细说明下这三个步骤。
消息发送
- _objc_msgSend (objc-msg-arm64.s)
ENTRY _objc_msgSend
//1、判断消息接收者是否为空
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
......
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
- lookUpImpOrForward (objc-runtime-new.mm)
// 遍历循环
for (unsigned attempts = unreasonableClassCount();;) {
// 2、在类对象的缓存中查找
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
// 如果缓存中有,则直接调用
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else { // 3、去 class_rw_t 找
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
// 7、已经动态解析,方法还没有找到,进入消息转发
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
......
// 4、去父类的 cache 中找
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
// 5、在父类中找到,缓存到 receiver 的类对象
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// 6、方法未找到,若未进行过动态解析,则进入动态解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
1、在消息发送流程,会首先判断下 receiver 是否为空,若为空则直接退出。基于此,在 OC 中对 nil 发送消息是不会报错的:
NSObject *obj = nil;
[obj class];
2、若 receiver 不为空,则会根据 receiver 的 isa 找到类对象,在类对象的 cache 去查找,若找到了直接调用;找不到则会去类对象的 class_rw_t 中查找。
3、若在 class_rw_t 中找到则直接调用,并缓存到类对象的 cache 中;若找不到则会通过 superclass 指针去父类的类对象的 cache 中查找。
4、若在 superclass 类对象 cache 中查找到,则直接调用并缓存的 receiver 的类对象的 cache 中;若没找到则在 superclass 类对象的 class_rw_t 中查找。
5、若在 class_rw_t 中找到则直接调用,并缓存的 receiver 的类对象的 cache 中;若找不到则会通过 superclass 指针去更上一层的父类类对象的 cache 中查找。
6、此后,一直重复 4、5 步,直到基类,如果到基类依然找不到方法实现,先看是否进行过动态解析,没进行过动态解析,则会进入第二步:动态解析。
7、若已经动态解析过,进入第三部:消息转发。
流程图:
动态解析
1、在动态解析阶段,首先判断是否进行过动态解析,是则进入下一阶段:消息转发;若不是,则调用 +resolveInstanceMethod:
或 +resolveClassMethod:
来进行动态解析。
2、标记为已动态解析过。
3、重新进入第一阶段 - 消息发送。
消息转发
1、调用 forwardingTargetForSelector:
方法,返回值不为 nil,调用 objc_msgSend(返回值, SEL)
;返回值为 nil ,进入下一步。
2、调用 methodSignatureForSelector:
,返回值不为nil ,调用 forwardInvocation:
方法;返回值为 nil,调用 doesNotRecognizeSelector:
程序崩溃。
至此,消息流程走完。