OC一直以来被称为动态语言,Objective-C 是一门动态的语言,在学习KVO和KVC的时候就能理解一些了,当然不单单是因为这个特质,OC还具有动态绑定,动态类型等特点,接下来学习的消息传递和消息转发机制就能深刻的体会这些特别的动态特点。
简单理解一下动态绑定和动态类型的意思
动态类型:Objective-C
中的对象是动态类型的,这意味着你可以在运行时动态地发送消息给对象,并且对象可以根据接收到的消息来执行相应的方法。这与静态类型语言不同,静态类型语言在编译时需要明确指定方法的调用。
动态绑定:Objective-C 使用动态绑定来实现方法调度。在运行时,Objective-C 运行时系统会根据消息的接收者来确定要调用的方法。这意味着你可以在运行时决定调用哪个方法,而不需要在编译时就确定。
Objective-C是一门动态的语言,以至于确定调用哪个方法被推迟到了运行时,而非编译时。与之相反,C语言使用静态绑定,也就是说在编译期就能决定程序运行时所应该调用的函数,所以在C语言中, 如果某个函数没有实现,编译时是不能通过的。而Objective-C是相对动态的语言,运行时还可以向类中动态添加方法,所以编译时并不能确定方法到底有没有对应的实现,编译器在编译期间也就不能报错。
Swift
当在对象上调用方法在Objective-C里面非常普遍,这个就叫给某个对象发送消息,消息有名称
或者选择子
这个说法,消息也不单单可以接受参数,还可以有返回值。
在Objective-C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用那个方法则完全运行期决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。
给某个对象发送消息
id returnValue = [someObject messageName:parameter];
Objective-C
中,[someObject messageName:parameter]
是发送消息的语法。这条语句表示向 someObject
对象发送名为 messageName
的消息,并传递参数 parameter
。Objective-C
是一门动态的语言,编译器在编译时无法确定消息的接收者 someObject
的具体类型和消息的具体实现。因此,需要在运行时通过 Objective-C
运行时系统来解析消息并执行相应的方法。在运行时,编译器会把上面这个格式的方法调用转化为一条标准的C语言函数调用实现:objc_ msgSend()
,该函数是消息objc里在运行时传递机制中的核心函数。void objc_msgSend(id self, SEL cmd, ....)
objc_msgSend
() 函数的作用是将消息发送给对象,并执行相应的方法。它接受多个参数,其中包括消息的接收者对象、要调用的方法的选择器以及任何方法的参数。根据选择器,运行时系统会找到接收者对象的方法实现,并在运行时动态调用该方法。运行时,上面Objc的方法调用会被翻译成一条C语言的函数调用, 如下:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter)
在 Objective-C
中,选择子(Selector
)是用于表示方法名的数据类型。它是一个在运行时由编译器生成的唯一的标识符,用于在对象中查找并调用相应的方法。
OC在编译时会根据方法的名字(包括参数序列),生成一个用来区分这个办法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么他们的ID就是相同的。所以不管是父类还是子类,名字相同那么ID就是一样的
在Runtime
中维护了一个SEL的表,这个表存储SEL不按照类来存储,只要相同的SEL就会被看做一个,并存储到表中。在项目加载时,会将所有方法都加载到这个表中,而动态生成的方法也会被加载到表中。
什么是IMP?
IMP:一个函数指针,保存了方法地址
IMP是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时(Objective-C runtime)寻找匹配此消息的IMP,然后调用它;但有些时候我们希望获取到IMP进行直接调用。
声明一个IMP:
typedef id (&IMP)(id,SEL,...);
IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针),调用方法的选标SEL(方法名),以及不定个数的方法参数,并返回一个id.
SEL
和 IMP
是用于表示方法的选择子和方法的实现的类型。@selector
关键字可以将方法名转换为 SEL 类型。联系:
总结: SEL 是方法名的唯一标识符,用于查找方法的实现,而 IMP 是指向方法实现代码的函数指针。它们共同构成了 Objective-C 运行时系统中的方法调用机制,实现了 Objective-C 的动态特性。
OC是一门动态的语言,在编译的时候不知道具体类型,运行的时候才会检查数据类型,根据函数名找到函数的实现,实现语言动态的就是runtime。核心有二:
objc_msgsend
函数进行消息的发送,通过SEL
查找IMP
的实现过程(方法名查找方法实现)消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。
作为发送消息的核心主人公,他的逻辑如下
id objc_msgSend(id self, SEL _cmd, ...) {
Class class = object_getClass(self);
IMP imp = class_getMethodImplementation(class, _cmd);
return imp ? imp(self, _cmd, ...) : 0;
}
objc_msgSend()
函数的一个简化版本的示例实现。该函数用于在 Objective-C 运行时发送消息并调用方法。self
对象的类(Class):使用 object_getClass(self)
函数获取 self
对象的类对象。这个类对象用于在运行时获取方法的实现。class_getMethodImplementation()
函数根据类对象和选择器 _cmd
获取对应方法的实现。这个函数返回一个指向方法实现的函数指针(IMP)。imp
调用该方法的实现。使用 imp()
函数并传递 self
、_cmd
和其他可能的参数进行方法调用。这只是 objc_msgSend()
函数的一个简化版本示例,实际的 objc_msgSend()
函数会处理更多的细节,包括消息转发、方法缓存等。
objc_msgSend
在调用的时候有两个默认参数,第一个参数是消息的接收者,第二个参数是方法名。如果方法本身有参数,会把本身的参数拼接到这两个参数后面。objc_msgSend()源码是用汇编语言写的,它针对不同的架构实现也不同,汇编语言的效率比c/c++更快,它直接对寄存器进行访问和操作,相比较内存的操作更加底层效率更高。
Apple在汇编方法的命名之前都是以下划线开头的,防止了符号的冲突。
看看函数的核心部分代码
//进入objc_msgSend流程
ENTRY _objc_msgSend
//流程开始,无需frame
UNWIND _objc_msgSend, NoFrame
//判断p0(消息接收者)是否存在,不存在则重新开始执行objc_msgSend
cmp p0, #0 // nil check and tagged pointer check
//如果支持小对象类型,返回小对象或空
#if SUPPORT_TAGGED_POINTERS
//b是进行跳转,b.le是小于判断,也就是p0小于0的时候跳转到LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//等于,如果不支持小对象,就跳转至LReturnZero退出
b.eq LReturnZero
#endif
//通过p13取isa
ldr p13, [x0] // p13 = isa
//通过isa取class并保存到p16寄存器中
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
该段代码是 是objc_msgSend
开始到找类对像cache
方法结束的流程。其中还有if else的原型
首先判断receiver
是否存在,以及是否是taggedPointer
类型的指针,如果不是taggedPointer
类型,我们就取出对象的isa
指针(x13寄存器中),通过isa指针找到类对象(x16寄存器),然后通过CacheLookup
,在类对象的cache
中查找是否有方法缓存,如果有就调用,如果没有走objc_msg_uncached
分支。
receiver
是否存在,为nil
则不做任何处理receiver
的isa指针找到对应的class
类对象class
类对象进行内存平移,找到cache
cache
中获取buckets
buckets
中对比参数sel
,看在缓存里有没有同名方法总的来说,消息发送会先通过缓存快速查找方法实现,如果缓存中没有找到,则进入慢速查找过程,从类的方法列表、父类链等逐级查找,直到找到匹配的方法实现或者最终抛出异常。
这里需要说明一下buckets:buckets
是缓存(cache
)中的一部分。
bucket
,每个 bucket
可以存储一个方法选择子(SEL
)及其对应的方法实现(IMP
)。bucket
,逐个对比方法选择子来实现的。如果找到匹配的方法选择子,就可以直接调用相应的方法实现,从而避免了慢速查找的过程。buckets
可以看作是缓存中存放方法选择子和对应方法实现的槽位。具体的实现方式可能因不同的编译器和平台而有所不同,但它们的目标都是提供一种快速查找的机制,避免每次消息发送都进行完整的查找过程。简单理解一下缓存的概念,上面的总结都提到了缓存这个概念
如果一个方法被调用了,那个这个方法有更大的几率被再此调用,既然如此直接维护一个缓存列表,把调用过的方法加载到缓存列表中,再次调用该方法时,先去缓存列表中去查找,如果找不到再去方法列表查询。这样避免了每次调用方法都要去方法列表去查询,大大的提高了速率
_lookUpImpOrForward
是 Objective-C 运行时中的一个函数,用于在消息发送过程中进行慢速查找或转发操作。
当消息发送的快速查找过程无法找到匹配的方法实现时,就会进入 _lookUpImpOrForward
函数。这个函数的主要功能是根据给定的接收者对象、方法选择子,以及其他相关信息,在运行时中查找适当的方法实现,并进行后续处理。
_lookUpImpOrForward
函数的具体行为取决于对象所处的状态和环境。它可能执行以下操作之一:
_lookUpImpOrForward
函数是 Objective-C 运行时中非常重要的一环,它确保在消息发送过程中能够处理各种情况,保证对象能够正确地响应方法调用。具体的实现细节可能因编译器和平台而有所不同,但其核心目标是在运行时中查找适当的方法实现或触发消息转发机制。
方法实现简单看看
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// The first message sent to a class is often +new or +alloc, or +self
// which goes through objc_opt_* or various optimized entry points.
//
// However, the class isn't realized/initialized yet at this point,
// and the optimized entry points fall down through objc_msgSend,
// which ends up here.
//
// We really want to avoid caching these, as it can cause IMP caches
// to be made with a single entry forever.
//
// Note that this check is racy as several threads might try to
// message a given class for the first time at the same time,
// in which case we might cache anyway.
behavior |= LOOKUP_NOCACHE;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
// 检查当前类是个已知类
checkIsKnownClass(cls);
// 确定当前类的继承关系
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookup the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
// 如果是常量优化缓存
// 再一次从cache查找imp
// 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
imp = cache_getImp(curClass, sel); //cache中找IMP
if (imp) goto done_unlock; //找到就直接返回了
curClass = curClass->cache.preoptFallbackClass();
#endif
} else { //如果不是常量优化缓存
// 当前类的方法列表。
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
// 每次判断都会把curClass的父类赋值给curClass
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// 如果超类链中存在循环,则停止。
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass 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;
}
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) == 0)) {
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
该代码是Objective-C 运行时中的 _lookUpImpOrForward
函数的部分实现。这个函数在消息发送过程中进行慢速查找或转发操作。
首先,代码检查接收者 inst
是否为空,如果为空则直接返回空。
接下来,代码根据接收者的类对象 cls
进行一系列的处理和查找操作,以找到适当的方法实现 imp
。这包括:
LOOKUP_NOCACHE
标志添加到 behavior
中,避免缓存查找。realizeAndInitializeIfNeeded_locked
函数对类对象进行实例化和初始化处理,确保类对象已经准备就绪。done
标签处。forward_imp
赋给 imp
。LOOKUP_RESOLVER
标志,说明需要调用方法解析器进行进一步处理,跳转到 resolveMethod_locked
函数进行解析。LOOKUP_NOCACHE
标志,将找到的方法实现 imp
缓存到类对象的缓存中。最后,代码解锁运行时锁,根据需要返回找到的方法实现 imp
或空值。
_lookUpImpOrForward的核心
// unreasonableClassCount()表示循环的上限;
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
// 如果是常量优化缓存
// 再一次从cache查找imp
// 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass方法列表。
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
// 每次判断都会把curClass的父类赋值给curClass
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// 没有找到实现,方法解析器没有帮助。
// 使用转发。
imp = forward_imp;
break;
}
}
// 如果超类链中存在循环,则停止。
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 超类缓存。
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// 在超类中找到forward::条目。
// 停止搜索,但不要缓存;调用方法
// 首先为这个类解析器。
break;
}
if (fastpath(imp)) {
// 在超类中找到方法。在这个类中缓存它。
goto done;
}
}
上述代码段是 _lookUpImpOrForward
函数中的一部分,用于在类对象的继承链中进行方法查找。
代码首先使用 unreasonableClassCount()
函数确定了循环的上限次数。然后进入一个无限循环。
在循环中,首先检查当前类对象的缓存是否是常量优化缓存(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
),表示在父类的缓存中找到了转发的方法实现。这时会停止循环,但不会将转发的方法实现缓存,而是先调用方法解析器来处理。
最后,在循环结束后,会根据需要将找到的方法实现缓存到类对象的缓存中,然后解锁运行时锁,并根据需要返回找到的方法实现或空值。
该代码段展示了在类对象的继承链中进行方法查找的过程。它首先尝试从缓存中获取方法实现,然后逐级向上查找,直到找到匹配的方法实现或到达继承链的顶端。如果找不到匹配的方法实现,则采用转发的方式处理。
method list
(二分查找/遍历查找)查找impcache
查找imp
(汇编)method
list
(二分查找/遍历查找)查找imp …继承链遍历…(父类->…->根父类)里找cache和method list的impnil
,指定imp
为消息转发,跳出循环,执行动态决议resolveMethod_locked(消息转发的内容)在消息发送之后还是没有找到方法,我们会进行消息转发机制,但是在这之前,我们还会进行一个步骤,叫动态决议
当本类和本类继承链下的cache
和method list
都查找不到imp
,imp
被赋值成了_objc_msgForward_impcache
但是它没有调用,会进入动态方法解析流程,并且只会执行一次。
resolveMethod_locked
是 Objective-C 运行时中的方法解析器的实现之一。它用于解决在消息发送过程中找不到方法实现的情况。下面是 resolveMethod_locked
函数的代码:
/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//判断是不是元类
if (! cls->isMetaClass()) {
// 不是元类,则是实例方法的动态方法解析
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// 是元类,则是类方法的动态方法解析
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls); // inst:类对象 cls: 元类
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
上述代码流程如下:
_class_resolveInstanceMethod
进行对象方法动态解析_class_resolveClassMethod
进行类方法动态解析resolveInstanceMethod
和resolveClassMethod
。也称为方法的动态决议。
上述执行resolveMethod_locked
方法后返回lookUpImpOrForwardTryCache
本质上调用了_lookUpImpTryCache
方法
在_lookUpImpTryCache
里面发现可以看到这里有cache_getImp
;也就是说在进行一次动态决议之后,还会通过cache_getImp
从cache
里找一遍方法的sel
。
如果(imp == NULL)?
也就是无法通过动态添加方法的话,还会执行一次lookUpImpOrForward
,这时候进lookUpImpOrForward
方法,这里behavior
传的值会发生变化。
第二次进入lookUpImpOrForward
方法后,执行到if (slowpath(behavior & LOOKUP_RESOLVER))
这个判断时
// 这里就是消息转发机制第一层的入口
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
根据变化后的behavior
值和LOOKUP_RESOLVER
值之间的关系导致该if语句内部只能进入第一次,因此这个判断相当于单例。解释了为什么开头说的该动态解析resolveMethod_locked
为什么只执行一次。
如果系统在动态决议阶段没有找到实现,就会进入消息转发阶段。分别是消息的快速转发和消息的慢速转发。
当cache
没有找到imp
,类的继承链里的方法列表都没有找到imp
,并且resolveInstanceMethod
/ resolveClassMethod
返回NO就会进入消息转发。
转发的作用在于,如果当前对象无法响应消息,就将它转发给能响应的对象。
当一个对象无法响应某个特定的方法时,Objective-C
运行时会自动调用该对象的forwardingTargetForSelector:
方法,给开发者一个机会返回一个能够响应该方法的对象。该方法的签名如下:
- (id)forwardingTargetForSelector:(SEL)aSelector;
开发者可以在该方法中根据需要返回一个实现了该方法的对象,使得该对象能够接收并处理该消息。返回的对象会被用于接收消息,并执行对应的方法。如果返回nil,则进入下一步的消息转发机制。
快速转发也是消息接受者替换,所以如果本类没有能力去处理这个消息,那么就转发给其他的类,让其他类去处理。
//第二根稻草,使用快速消息转发,找其他对象来实现方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(methodTwo)) {
//也就是本类中的其他对象,此处选取的对象是forwardObject
return self.forwardObject;
}
return nil;
}
如果forwardingTargetForSelector
方法返回的是nil
,那么我们还有最后一根稻草可以抓住完全消息转发。相比于快速转发,不仅可以替换消息接受者,还能替换方法:
//第三根稻草,使用完全消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(methodThree)) {
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sig;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//选择一个函数去替换
anInvocation.selector = @selector(methodNormal);
//选择一个消息接收者(对象)去替换
anInvocation.target = self.forwardObject;
[anInvocation invoke];
}
这里有两个类,NSMethodSignature
和 NSInvocation
。其中NSMethodSignature
是方法签名,可以通过方法的字符来实例化。NSInvocation
是方法调用实体,其中有target
和selector
和参数构成。
这第三个救命稻草的逻辑就是: 先判断methodSignatureForSelector
有没有被实现且返回值不为nil
,如果已经实现且返回值不为nil
,那么就进行下一步判断forwardInvocation
有没有被实现,如果forwardInvocation
已经实现那么就使用方法签名生成NSInvocation
对象并调用forwardInvocation
方法,最后返回forwardInvocation
执行的结果,如果forwardInvocation
方法没有被实现,那就直接调用doesNotRecognizeSelector
方法打印日志抛出异常。如果methodSignatureForSelector
没有被实现或返回值为nil,那么就直接调用doesNotRecognizeSelector
方法打印日志抛出异常。
需要注意的是,forwardInvocation
:方法在methodSignatureForSelector
:方法返回非nil的方法签名时才会被调用。如果methodSignatureForSelector
:返回了nil,那么forwardInvocation
:将不会被触发,而是进入消息转发的最后一步:动态方法解析或抛出异常。
快速:
- (id)forwardingTargetForSelector:(SEL)aSelector {
慢速:
// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 正向调用
- (void)forwardInvocation:(NSInvocation *)anInvocation;
消息转发机制基本上分为三个步骤,也被称为消息的三次拯救:
resolveInstanceMethod
或者resolveClassMethod
方法,尝试给一个没有实现的方法添加实现(动态解析)forwardingTargetForSelector
尝试让本类中其他的对象去执行这个函数(快速地消息转发)methodSignatureForSeletor
和 frowardInvocation
来进行完全消息转发,不仅可以替换消息接受者,还能替换方法。(完整的消息转发)寒假的学习还是比较浅的,因为没有接触到OC的类和对象的底层,慢慢学习了元类父类等关系和一些底层的原理,看消息发送和转发机制就比较轻松了。