自己做网站的成本,做网站需要前置审批,青岛信息网,亿网行消息传递是什么
Objective-C是一种动态类型语言#xff0c;这意味着在编译时并不确定对象的具体类型#xff0c;而是在运行时决定。消息传递机制允许程序在运行时向对象发送消息#xff0c;对象再决定如何响应这些消息。
当你通过对象调用方法时#xff0c;例如像这样[ob…消息传递是什么
Objective-C是一种动态类型语言这意味着在编译时并不确定对象的具体类型而是在运行时决定。消息传递机制允许程序在运行时向对象发送消息对象再决定如何响应这些消息。
当你通过对象调用方法时例如像这样[obj someMethod]编译器会将其转换为一个消息发送的底层调用通常是 objc_msgSend(obj, selector(someMethod))。这个函数接受两个主要参数方法的调用者和方法选择器也就是方法名)。
void objc_msgSend(id self, SEL cmd, ....第一 个参数代表接收者也就是方法调用者第二个参数代表方法选择器(SEL 是选择子的类型)也就是方法的名字后续参数就是消息中的 那些参数其顺序不变。
选择子SEL
选择子Selector是用于表示方法名的数据类型。它是一个在运行时由编译器生成的唯一的标识符用于在对象中查找并调用相应的方法。
OC在编译时会根据方法的名字包括参数序列生成一个用来区分这个方法的唯一的一个ID这个ID就是SEL类型的。我们需要注意的是只要方法的名字包括参数序列相同那么他们的ID就是相同的。所以不管是父类还是子类名字相同那么ID就是一样的
IMP
IMP是一个函数指针它指向方法的实际实现。当运行时系统找到了与选择器相匹配的方法时它会获取该方法的IMP然后调用这个函数指针来执行方法的代码。
IMP通常被声明为id返回类型和接受id类型的self和SEL类型的_cmd参数的函数指针。例如
typedef id (*IMP)(id, SEL, ...);SEL与IMP的关系
在运行时系统中SEL和IMP是紧密相关的。当你调用一个方法时运行时系统首先将方法名解析为SEL然后使用这个SEL去查找与之对应的IMP。一旦找到IMP运行时系统就会调用这个函数指针来执行方法的代码。
消息传递的流程 首先Runtime系统会通过obj的 isa 指针找到其所属的class 接着在这个类的缓存中查找与选择器匹配的方法实现 如果缓存中没找到接着在这个类的方法列表method list中查找与选择器someMethod匹配的方法实现IMP。 如果在当前类中没有找到Runtime会沿着类的继承链往它的 superclass 中查找也是先查缓存再查方法列表直到到达根类通常为 NSObject。 一旦找到someMethod这个函数就去执行它的实现IMP 。 如果直到根类还是没找到就会进行消息转发流程。
消息发送Messaging是 Runtime 通过 selector 快速查找 IMP 的过程有了函数指针就可以执行对应的方法实现消息转发Message Forwarding是在查找 IMP 失败后执行一系列转发流程的慢速通道如果不作转发处理则会打日志和抛出异常。
objc_msgSend()的伪代码如下
id objc_msgSend(id self, SEL _cmd, ...) {//获取当前对象的类对象用于在运行时获取方法实现Class class object_getClass(self);//根据类对象和选择器_cmd获取IMP指针IMP imp class_getMethodImplementation(class, _cmd);//判空IMP并返回该方法实现return imp ? imp(self, _cmd, ...) : 0;
}
上面代码之所以是伪代码是因为objc_msgSend 是用汇编语言写的针对不同架构有不同的实现。汇编语言的效率比c/c更快它直接对寄存器进行访问和操作相比较内存的操作更加底层效率更高。
其源代码比较多下面是核心部分
ENTRY _objc_msgSend // 开始_objc_msgSend函数的定义
MESSENGER_START // 标记消息传递的开始NilTest NORMAL // 检查self是否为nil如果是nil则引发异常GetIsaFast NORMAL // 快速获取self对象的isa指针isa指向对象的类信息// r11 self-isaCacheLookup NORMAL // 在缓存中查找IMP如果找到则直接调用IMP// 这里利用了方法缓存以提高性能NilTestSupport NORMAL // 如果self是nil的备用处理GetIsaSupport // 如果需要更全面地获取isa信息// 这是为了支持特殊情况下的isa获取// cache miss: go search the method lists
LCacheMiss: // 缓存中没找到开始在方法列表中查找IMP// isa仍然在r11寄存器中MethodTableLookup %a1, %a2 // 在方法表中查找与选择器匹配的IMP// r11 IMP这里查找方法实现cmp %r11, %r11 // 设置eq标志位用于非 stret (simple) 的方法转发// 这里比较r11与自身实际上是设置条件码用于判断是否需要转发jmp *%r11 // 跳转到IMP地址并执行// 这里直接调用了方法实现END_ENTRY _objc_msgSend // 结束_objc_msgSend函数的定义MethodTableLookup 宏是重点负责在缓存没命中时在方法表中负责查找 IMP:
.macro MethodTableLookupMESSENGER_END_SLOWSaveRegisters// _class_lookupMethodAndLoadCache3(receiver, selector, class)movq $0, %a1movq $1, %a2movq %r11, %a3call __class_lookupMethodAndLoadCache3// IMP is now in %raxmovq %rax, %r11RestoreRegisters.endmacro从上面的代码可以看出方法查找 IMP 的工作交给了 OC 中的 _class_lookupMethodAndLoadCache3 函数并将 IMP 返回从 r11 挪到 rax。最后在 objc_msgSend 中调用 IMP。
消息传递快速查找 首先检查消息接收者receiver是否存在不存在则不做任何处理。 接着通过receiver的isa指针找到对应的class类对象 从cache中获取buckets从buckets中对比参数sel看在缓存里有没有同名方法如果buckets中有对应的SEL则返回它对应的IMP 如果在缓存中没有找到匹配的方法选择子则执行慢速查找过程即调用 _objc_msgSend_uncached 函数并进一步调用 _lookUpImpOrForward 函数进行全局的方法查找。
简单来说快速查找就是在所属类的缓存中查找SEL对应的IMP指针 buckets是cache中的结构体bucket_t的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针指向了一个方法的具体实现。 cache_t中的bucket_t *_buckets其实就是一个散列表用来存储Method的链表。 消息传递慢速查找
当消息发送的快速查找过程无法找到匹配的方法实现时就会进入 _lookUpImpOrForward 函数根据继承链和协议信息逐级查找方法。
从本类的 method list **(二分查找/遍历查找)**查找imp从本类的父类的cache查找imp**(汇编)**从本类的父类的method list **(二分查找/遍历查找)**查找imp …继承链遍历…父类-…-根父类里找cache和method list的imp若上面环节有任何一个环节查找到了imp跳出循环缓存方法到本类的cache并返回imp直到查找到nil指定imp为消息转发跳出循环执行动态决议resolveMethod_locked消息转发的内容
简单来说慢速查找就是在所属类的方法列表中查找SEL对应的IMP指针没找到就沿着继承链一直查找方法缓存和方法列表找到就返回IMP没找到就执行动态决议
lookUpImpOrForward函数
前面提到的 _class_lookupMethodAndLoadCache3 函数其实就是简单的调用了 lookUpImpOrForward 函数
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}注意 lookUpImpOrForward 调用时使用缓存参数传入为 NO因为之前快速查找的时候已经尝试过查找缓存了。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) 实现了一套查找 IMP 的标准路径也就是在消息转发Forward之前的逻辑。
下面是lookUpImpOrForward函数源码
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {const IMP forward_imp (IMP)_objc_msgForward_impcache; // 定义转发的IMPIMP imp nil; // 初始化IMP为nilClass curClass;runtimeLock.assertUnlocked(); // 确认运行时锁未被持有// 如果类未初始化设置LOOKUP_NOCACHE防止缓存单个条目的情况if (slowpath(!cls-isInitialized())) {behavior | LOOKUP_NOCACHE;}runtimeLock.lock(); // 锁住运行时锁// 验证类是否是已知的类checkIsKnownClass(cls);// 确保类已实现和初始化cls realizeAndInitializeIfNeeded_locked(inst, cls, behavior LOOKUP_INITIALIZE);curClass cls; // 当前类for (unsigned attempts unreasonableClassCount(); ;) {// 如果类的缓存是常量优化缓存// 再一次从cache查找imp// 目的防止多线程操作时刚好调用函数此时缓存进来了if (curClass-cache.isConstantOptimizedCache(true)) {
#if CONFIG_USE_PREOPT_CACHESimp cache_getImp(curClass, sel); // 从缓存中查找IMPif (imp) goto done_unlock; // 如果找到IMP结束循环curClass curClass-cache.preoptFallbackClass();
#endif} else {// 从当前类的方法列表中查找匹配的选择器method_t *meth getMethodNoSuper_nolock(curClass, sel);if (meth) {imp meth-imp(false); // 获取IMPgoto done;}// 每次判断都会把curClass的父类赋值给curClassif (slowpath((curClass curClass-getSuperclass()) nil)) {imp forward_imp; // 使用转发IMPbreak;}}// 防止超类链中出现循环if (slowpath(--attempts 0)) {_objc_fatal(Memory corruption in class list.);}// 在超类缓存中查找IMPimp cache_getImp(curClass, sel);if (slowpath(imp forward_imp)) {// 在超类中发现转发条目停止搜索break;}if (fastpath(imp)) {// 在超类中找到方法缓存结果goto done;}}// 尝试方法解析器if (slowpath(behavior LOOKUP_RESOLVER)) {behavior ^ LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}done:if (fastpath((behavior LOOKUP_NOCACHE) )) {
#if CONFIG_USE_PREOPT_CACHESwhile (cls-cache.isConstantOptimizedCache(true)) {cls cls-cache.preoptFallbackClass();}
#endiflog_and_fill_cache(cls, imp, sel, inst, curClass); // 填充缓存}done_unlock:runtimeLock.unlock(); // 解锁// 如果设置LOOKUP_NIL并且IMP是转发IMP则返回nilif (slowpath((behavior LOOKUP_NIL) imp forward_imp)) {return nil;}return imp; // 返回找到的IMP
}总体的流程如下
首先检查接收者 inst 是否为空如果为空则直接返回空。
接下来代码根据接收者的类对象 cls 进行一系列的处理和查找操作以找到适当的方法实现 imp。包括 检查类对象是否已经初始化如果尚未初始化则将 LOOKUP_NOCACHE 标志添加到 behavior 中避免缓存查找。 通过 realizeAndInitializeIfNeeded_locked 函数对类对象进行实例化和初始化处理确保类对象已经准备就绪。 使用循环逐级查找方法实现包括在类的缓存中查找、在类的方法列表中查找、在父类链中查找。如果找到了匹配的方法实现则跳转到 done 标签处。 如果在查找过程中找不到匹配的方法实现则说明需要进行消息转发。将消息转发的默认实现 forward_imp 赋给 imp。 如果设置了 LOOKUP_RESOLVER 标志说明需要调用方法解析器进行进一步处理跳转到 resolveMethod_locked 函数进行解析。 在查找或转发结束后如果未设置 LOOKUP_NOCACHE 标志将找到的方法实现 imp 缓存到类对象的缓存中。 代码解锁runtime锁根据需要返回找到的方法实现 imp 或空值
在循环中首先检查当前类对象的缓存是否是常量优化缓存isConstantOptimizedCache。如果是常量优化缓存代码尝试从缓存中获取方法实现cache_getImp(curClass, sel)。如果成功获取到方法实现则跳转到 done_unlock 标签处结束查找。
如果当前类对象的缓存不是常量优化缓存代码继续执行。通过调用 getMethodNoSuper_nolock 函数在当前类对象的方法列表中查找方法meth getMethodNoSuper_nolock(curClass, sel)。如果找到匹配的方法则获取对应的方法实现imp meth-imp(false)跳转到 done 标签处结束查找。
如果在当前类对象的方法列表中没有找到匹配的方法实现代码继续执行。将当前类对象的父类赋值给 curClass并判断是否为 nil。如果父类为 nil说明已经到达了继承链的顶端没有找到匹配的方法实现。此时将默认的转发实现 forward_imp 赋给 imp并跳出循环。
在循环的每次迭代中会将 attempts 的值减一表示尚未完成的查找次数。如果 attempts 的值减到零则说明类对象的继承链中存在循环这是不合理的。此时会触发一个错误终止程序执行。
如果在当前类对象的缓存中找到了转发的条目imp forward_imp表示在父类的缓存中找到了转发的方法实现。这时会停止循环但不会将转发的方法实现缓存而是先调用方法解析器来处理。
最后在循环结束后会根据需要将找到的方法实现缓存到类对象的缓存中然后解锁运行时锁并根据需要返回找到的方法实现或空值。
动态决议 resolveMethod_locked
当慢速查找依然没有找到IMP时会进入方法动态解析阶段源码如下
// 尝试方法解析器if (slowpath(behavior LOOKUP_RESOLVER)) {behavior ^ LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}这里调用了resolveMethod_locked方法下面是它的源代码
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{runtimeLock.assertLocked();ASSERT(cls-isRealized());//**加锁**runtimeLock.unlock();//**判断是否是元类**if (! cls-isMetaClass()) {//**不是元类调用resolveInstanceMethod方法**resolveInstanceMethod(inst, sel, cls);} else {//**是元类调用resolveClassMethod**resolveClassMethod(inst, sel, cls);//**如果调用上面的方法还没有找到尝试调用resolveInstanceMethod**//**原因是根据isa的继承链根元类的父类是NSObject所以在元类中如果没有找到**//**最后可能会在NSObjct中找到目标方法**if (!lookUpImpOrNilTryCache(inst, sel, cls)) {resolveInstanceMethod(inst, sel, cls);}}//**重新调用lookUpImpOrForwardTryCache方法返回方法查找流程**//**因为已经进行过一次动态方法决议下次将不会再进入所以不会造成死循环**return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
} 首先判断进行解析的是否是元类 如果不是元类则调用_class_resolveInstanceMethod进行实例方法动态解析 如果是元类则调用_class_resolveClassMethod进行类方法动态解析 b完成类方法动态解析后再次查询cls中的imp如果没有找到则进行一次对象方法动态解析 最后执行 lookUpImpOrForwardTryCache函数 resolveInstanceMethod和resolveClassMethod也称为方法的动态决议。 resolveInstanceMethod方法
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{runtimeLock.assertUnlocked();//**如果目标类没有初始化直接报错**ASSERT(cls-isRealized());//**创建一个方法名为resolveInstanceMethod的SEL**SEL resolve_sel selector(resolveInstanceMethod:);//**判断resolveInstanceMethod是否在目标类中实现**//**如果我们的类是继承自NSObject的话那么这个判断就永远为false**//**因为在NSObject中已经默认实现了resolveInstanceMethod方法**//**因为是去cls-ISA也就是元类中查找所以我们可以断定resolveInstanceMethod是个类方法**if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls-ISA(/*authenticated*/true))) {// Resolver not implemented.return;}//**强制类型转换objc_msgSend**BOOL (*msg)(Class, SEL, SEL) (typeof(msg))objc_msgSend;//**通过objc_msgSend方法调用resolveInstanceMethod**bool resolved msg(cls, resolve_sel, sel);//**拼接上LOOKUP_NIL参数后重新调用方法查找流程**//**虽然调用了resolveInstanceMethod,但是这个返回值是bool**//**所以我们要获取对应的imp还是需要通过方法查找流程**//**如果通过resolveInstanceMethod添加了方法就缓存在类中**//**没添加则缓存forward_imp**IMP imp lookUpImpOrNilTryCache(inst, sel, cls);//**组装相应的信息**if (resolved PrintResolving) {if (imp) {………}else {………}}
}
首先创建一个方法名为resolveInstanceMethod的SEL对象resolve_sel;
然后判断resolve_sel是否实现如果继承NSObject则必定已经实现这里通过cls-ISA可以知道resolveInstanceMethod是个类方法
通过objc_ msgSend直接调用resolveInstanceMethod方法因为是直接对cls发送消息所以也可以看出resolveInstanceMethods是类方法;
调用lookUpImpOrNilTryCache方法重新返回到方法查找的流程当中去;
resolveClassMethod方法
static void resolveClassMethod(id inst, SEL sel, Class cls)
{runtimeLock.assertUnlocked();ASSERT(cls-isRealized());ASSERT(cls-isMetaClass());//**判断resolveClassMethod是否实现NSObject中默认实现**if (!lookUpImpOrNilTryCache(inst, selector(resolveClassMethod:), cls)) {// Resolver not implemented.return;}//**获取目标类**Class nonmeta;{mutex_locker_t lock(runtimeLock);nonmeta getMaybeUnrealizedNonMetaClass(cls, inst);// initialize path should have realized nonmeta alreadyif (!nonmeta-isRealized()) {_objc_fatal(nonmeta class %s (%p) unexpectedly not realized,nonmeta-nameForLogging(), nonmeta);}}//**强制类型转换objc_msgSend**BOOL (*msg)(Class, SEL, SEL) (typeof(msg))objc_msgSend;//**通过objc_msgSend调用类中的resolveClassMethod方法**bool resolved msg(nonmeta, selector(resolveClassMethod:), sel);//**拼接上LOOKUP_NIL参数后重新调用方法查找流程**//**类方法实际上就是元类对象中的对象方法所以可以通过方法查找流程在元类中查找**//**如果通过resolveClassMethod添加了就缓存方法在元类中**//**没添加则缓存forward_imp**IMP imp lookUpImpOrNilTryCache(inst, sel, cls);if (resolved PrintResolving) {if (imp) {……}else {……}}
}
这个方法与resolveInstanceMethod比较类似如果通过resolveClassMethod方法添加了目标imp则将其缓存在目标元类中否则缓存forward_imp。
lookUpImpOrNilTryCache方法
在resolveInstanceMethod和resolveClassMethod中都会调用的lookUpImpOrNilTryCache方法
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方法
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_CACHESif (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实现。
如果系统在动态决议阶段没有找到实现就会进入消息转发阶段。