UI学习
消息传递和消息转发
SEL
就像是方法的 "名字",是一个字符串,用于在运行时查找方法。IMP
是方法的具体实现,是一个函数指针。_cmd
是方法内部的一个参数,代表当前正在执行的方法选择器。
选择子SEL
选择子(Selector)是用于表示方法名的数据类型。它是一个在运行时由编译器生成的唯一的标识符,用于在对象中查找并调用相应的方法。
OC在编译时会根据方法的名字(包括参数序列),生成一个用来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么他们的ID就是相同的。所以不管是父类还是子类,名字相同那么ID就是一样的
IMP
IMP是一个函数指针,它指向方法的实际实现。当运行时系统找到了与选择器相匹配的方法时,它会获取该方法的IMP,然后调用这个函数指针来执行方法的代码。
IMP通常被声明为id返回类型和接受id类型的self和SEL类型的_cmd参数的函数指针。
一般通过SEL来查找方法的IMP
快速查找是针对缓存,慢速查找是查找本类的方法列表和继承链的缓存和方法列表
简短总结:
【快速查找流程】
首先,在类的缓存cache
中查找指定方法的实现【慢速查找流程】
如果缓存中没有找到,则在类的方法列表
中查找,如果还是没找到,则去父类链的缓存和方法列表
中查找【动态方法决议】
如果慢速查找还是没有找到时,第一次补救机会
就是尝试一次动态方法决议
,即重写resolveInstanceMethod
/resolveClassMethod
方法【消息转发】
如果动态方法决议还是没有找到,则进行消息转发
,消息转发中有两次补救机会:快速转发+慢速转发
- 如果转发之后也没有,则程序直接报错崩溃
unrecognized selector sent to instance
消息传递的流程
- 首先,RunTime通过obj的isa指针找到其所属的class
- 接着在这个类的缓存中查找与选择器匹配的方法实现。(即快速查找)
- 如果缓存中没找到,那就在这个类的方法列表中查找与SEL匹配的IMP。
- 如果当前类没找到,Runtime会沿着类的继承链往他的superclass中查找,先查找缓存,在查找方法列表,直到根类。
- 一旦找到这个函数,就会执行他的IMP
- 如果知道根类都没有找到,则会进行消息转发流程。
消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。
快速查找是针对缓存,慢速查找是查找本类的方法列表和继承链的缓存和方法列表
消息传递
快速查找:
- 首先判断 receiver 是否为 nil 或 tagged pointer,若是则直接返回。
- 通过 receiver 的 isa 指针获取类对象,再通过类对象结构体偏移找到 cache_t 结构。
- cache_t 结构中包含 buckets(方法缓存表)和 mask(哈希掩码)。
- 用 cmd 的哈希值与 mask 做与运算,得到索引 index,定位到 buckets[index]。
- 检查 bucket 中的 sel 是否等于 cmd,若相等则命中(hit),返回 imp。
- 若不等,则采用线性探测法(index++,循环查找),直到找到空 bucket 或回到起始位置。
- 若遍历一圈未找到,则进入 jumpmiss,走慢速查找流程。
慢速查找 :进入 _lookUpImpOrForward
函数
- 若 cache 未命中,则进入 lookUpImpOrForward。
- 检查 cls 是否为有效类对象,否则报错。
- 若类未初始化,则先初始化。
- 遍历当前类的方法列表查找 cmd,若找到则返回 imp,并写入 cache。
- 若未找到,则递归查找父类,直到父类为 nil。
- 若最终未找到 imp,则尝试动态方法解析(resolveInstanceMethod)。
- 若解析失败,则进入消息转发流程(objc_msgForward)。
- 查找过程中每次命中 imp 都会写入 cache 以优化下次查找。
动态决议 resolveMethod_locked:
慢速查找没找到IMP时,进入方法动态解析。
-
首先判断进行解析的是否是元类
-
如果不是元类,则调用
_class_resolveInstanceMethod
进行实例方法动态解析 -
如果是元类,则调用用
_class_resolveClassMethod
进进行类方法动态解析如过没有找到,则在调用_class_resolveInstanceMethod
找实例方法,因为类是元类的实例。 -
当完成类方法动态解析后,再次查询cls中的imp,如果没有找到,则进行一次对象方法动态解析
-
最后执行
lookUpImpOrForwardTryCache
函数
resolveInstanceMethod
和resolveClassMethod
也称为方法的动态决议。
resolveInstanceMethod方法
- 针对
实例方法
调用,在快速-慢速查找均没有找到实例方法
的实现时,我们有一次挽救的机会,即尝试一次动态方法决议
,由于是实例方法
,所以会走到resolveInstanceMethod
方法 - 在
发送resolveInstanceMethod消息
前,需要查找cls类
中是否有该方法的实现
,即通过lookUpImpOrNil
方法又会进入lookUpImpOrForward
慢速查找流程查找resolveInstanceMethod
方法- 如果没有,则直接返回
- 如果有,则发送
resolveInstanceMethod
消息
- 再次慢速查找实例方法的实现,即通过
lookUpImpOrNil
方法又会进入lookUpImpOrForward
慢速查找流程查找实例方法
注意:此处进入慢速查找中,是查找实例方法动态解析是否实现。
之后调用lookUpImpOrNilTryCache
方法,重新返回到方法查找的流程当中去;
lookUpImpOrNilTryCache方法
在resolveInstanceMethod
和resolveClassMethod
中都会调用的lookUpImpOrNilTryCache
方法
objective-c
extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
//**这里behavior没传,所以是默认值0**
//**behavior | LOOKUP_NIL = 0 | 4 = 4**
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
给参数behavior
拼接上LOOKUP_NIL
然后调用_lookUpImpTryCache
方法
lookUpImpTryCache方法
objectivec
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//**判断类是否初始化**
if (slowpath(!cls->isInitialized())) {
//**没有初始化直接调用lookUpImpOrForward**
//**里面针对没初始化的类,有相关处理**
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//**去缓存中,查找sel是否有对应的imp**
IMP imp = cache_getImp(cls, sel);
//**找到了则跳去done**
if (imp != NULL) goto done;
//**没找到继续往下走,去共享缓存中查找**
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
//**没找到对应的imp,调用lookUpImpOrForward方法查找,behavior = 4**
//** 4 & 2 = 0 ,所以这次方法查找不会再次进行动态方法决议**
//**将_objc_msgForward_impcache缓存起来,方便下次直接返回**
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//**命中缓存中,并且sel对应的imp为_objc_msgForward_impcache**
//**说明动态方法决议已经执行过,且没有添加imp,则直接返回空**
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
//**说明动态方法决议中添加了对应的imp**
return imp;
}
-
首先判断类是否初始化,如果没有初始化则直接调用lookUpImpOrForward,里面有针对没初始化的类进行相应的处理;
-
然后去缓存中进行方法的快速查找,找到了就去done
-
缓存中没找到,如果支持共享缓存,则去共享缓存中查找
-
都没有查找到,则通过慢速方法查找去查找方法,由于behavior 的值发生改变,这次慢速查找不会再次调用动态方法决议
-
在done流程中,如果已经执行过动态方法决议且并没有添加imp,则缓存中的sel对应imp为_objc_msgForward_impcache,这时直接返回nil。否则返回添加的imp实现。
以上则是这个歌动态决议阶段,如果都没找到实现,则进入消息转发流程:
消息转发
消息转发的方法有三个:
- 【快速转发】
forwardingTargetForSelector
- 【慢速转发】
methodSignatureForSelector
forwardInvocation

方法签名
方法签名本质上是一个 NSMethodSignature
对象,它封装了以下信息:
- 返回值类型 :例如
int
、NSString*
、void
等 - 参数数量
- 每个参数的类型
- 方法调用的调用约定(Calling Convention)
在底层,这些类型信息通常使用 Type Encodings 来表示,这是一种将类型信息编码为字符串的方式。例如:
i
表示int
@
表示id
:
表示SEL
v
表示void