赞
踩
前文学习了OC的类和对象的底层原理,看到结构体中涉及到方法列表,特此来学习一下消息发送以及消息转发
OC中的对象是动态类型的,这意味着我们可以在运行时发送消息给对象,对象可以根据接收到的消息执行相应的方法。与静态语言类型不同,静态类型在编译时就必须要确定引用哪种对象,而动态类型使用更加泛化
id someObject = [[NSString alloc] initWithString:@"Hello, World!"];
someObject = [[NSDate alloc] init];
动态绑定是指方法调用可以在运行时解析,而不是在编译时。这意味着 Objective-C 在运行时决定要执行对象的哪个方法,而不是在编译时。这种机制是通过消息传递(而非直接函数调用)实现的,使得你可以在程序运行期间改变对象的调用的方法
OC能被称为动态语言的一个核心点就是消息转发机制,消息转发机制允许开发者截取并处理未被对象识别的消息。
这就使得即使某个方法或是函数没有被实现,编译时也不会报错,因为运行时还可以动态地添加方法
OC对象调用方法,其本质就是发送消息,消息有名称与选择自,还可以有返回值
我们先前说过了OC是一门动态语言,其方法在在底层被编译成调用objc_msgSend
函数. 这也就是我们在 Runtime
所提到的 消息发送机制
调用方法后编译为C++的源码部分
运行时,上面Objc的方法调用会被翻译成一条C语言的函数调用, 如下:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter)
我们可以看到消息发送的核心是这个函数
void objc_msgSend(id self, SEL cmd, ....)
我们来逐步分析一下各个参数
在上述苹果官网公开源码objc4的objc.h文件中,定义如下:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL
:一个不透明的类型,代表方法的选择器/选择子。定义如下:
在源码中没有直接找到objc_selector
的定义,从一些书籍上与Blog上看到可以将SEL
理解为一个char*
指针。
// GNU OC 中的 objc_selector
struct objc_selector {
void *sel_id;
const char *sel_types;
};
SEL实际上就是一个方法选择器,告诉编译器我们当前想要调用哪一个方法
在运行时,方法选择器用来表示方法的名字。一个方法选择器就是一个C字符串,在OC的运行时被注册。编译器生成选择器在类加载时由运行时自动映射。
可以在运行时添加新的选择器,并使用sel_registerName
函数检索现有的选择器。
获取SEL的三种方式
注意:
OC在编译时会根据方法名字生成唯一一个区分的ID,这个ID是SEL类型的,只要方法名字相同,SEL返回的就相同
在Runtime
中维护了一个SEL
的表,这个表按NSSet
来存储,只要相同的SEL就会被看作是同一个方法并被加载到表中
因此OC中需要避免方法重载
指向方法实现
的首地址的指针。源码里实现如下:(可以看得出来是对方法类型
起了一个别名。)
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP的数据类型是指针,指向方法实现开始的位置
有时候为了避免传递之间的开销,我们希望直接调用IMP,省去了一些列的查找,直接向对象发送消息,效率会高一些。
// 根据代码块获取IMP, 其实就是代码块与IMP关联
IMP imp_implementationWithBlock(id block)
// 根据Method获取IMP
IMP method_getImplementation(Method m)
// 根据SEL获取IMP
[[objc Class] instanceMethodForSelector:SEL]
当我们获取一个方法的IMP后,可以直接调用IMP:
IMP imp = method_getImplementation(Method m);
// result保存方法的返回值,id表示调用这个方法的对象,SEL是Method的选择器,argument是方法的参数。
id result = imp(id, SEL, argument);
一个不透明的类型,表示类中定义的方法。定义如下:
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
可以看出Method是一个结构体类型指针,objc_method结构中有三个属性
method_name
:SEL类型(选择器),表示方法名的字符串。
method_types
:char*类型的,表示方法的类型;包含返回值和参数的类型。
method_imp
:IMP类型,指向方法实现地址的指针。
Method操作函数如下:
方法操作主要有以下函数:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); // 添加方法
Method class_getInstanceMethod(Class cls, SEL name); // 获取实例方法
Method class_getClassMethod(Class cls, SEL name); // 获取类方法
Method *class_copyMethodList(Class cls, unsigned int *outCount); // 获取所有方法的数组
// 替代方法的实现
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
// 交换两个方法的实现
method_exchangeImplementations(Method m1, Method m2)
SEL
:方法选择器,实际上就是一个字符串的名字
IMP
:指向方法实现首地址的指针
Method
:是一个结构体,包含一个SEL表示方法名、一个IMP指向函数的实现地址、一个Char*表示函数的类型(包括返回值和参数类型)
SEL
、IMP
、Method
之间的关系
当向对象发送消息时,调用SEL
在对象的类以及父类方法列表中进行查找Method
,因为Method
结构体中包含IMP
指针,因此一旦找到对应的Method
就直接调用IMP
去实现方法
接下来我们对发送消息后的操作进行一次流程概述
_objc_msgSend_uncached
函数,并进一步调用 _lookUpImpOrForward
函数进行全局的方法查找。IMP
,就去对应的类中的方法列表中寻找,如果还是没有找到就循着SuperClass
继承链往上查找父类的缓存列表以及方法列表,找到了则将方法写入当前类的缓存中resolveInstanceMethod
/resolveClassMethod
方法,然后再次进行一次前面的查找流程,并且设置标识符表示已经调用过一次动态方法解析,后面不会再次调用forwardingTargetForSelector
方法获取新的对象作为receiver
重新执行 selector
(即使用新的对象发送消息,然后重新执行上面的步骤),如果返回的内容不合法(为 nil 或者跟旧 receiver 一样),那就进入第二步methodSignatureForSelector
获取方法签名后,判断返回类型信息是否正确,再调用 forwardInvocation
执行 NSInvocation
对象,并将结果返回。如果对象没实现 methodSignatureForSelector
方法,则最后会报错首先我们来到消息发送的源码,我们可以看到源码是由汇编实现的
//---- 消息发送 -- 汇编入口--objc_msgSend主要是拿到接收者的isa信息 ENTRY _objc_msgSend //---- 无窗口 UNWIND _objc_msgSend, NoFrame //---- p0 和空对比,即判断接收者是否存在,其中p0是objc_msgSend的第一个参数-消息接收者receiver cmp p0, #0 // nil check and tagged pointer check //---- le小于 --支持taggedpointer(小对象类型)的流程 #if SUPPORT_TAGGED_POINTERS b.le LNilOrTagged // (MSB tagged pointer looks negative) #else //---- p0 等于 0 时,直接返回 空 b.eq LReturnZero #endif //---- p0即receiver 肯定存在的流程 //---- 根据对象拿出isa ,即从x0寄存器指向的地址 取出 isa,存入 p13寄存器 ldr p13, [x0] // p13 = isa //---- 在64位架构下通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息 GetClassFromIsa_p16 p13 // p16 = class LGetIsaDone: // calls imp or objc_msgSend_uncached //---- 如果有isa,走到CacheLookup 即缓存查找流程,也就是所谓的sel-imp快速查找流程 CacheLookup NORMAL, _objc_msgSend #if SUPPORT_TAGGED_POINTERS LNilOrTagged: //---- 等于空,返回空 b.eq LReturnZero // nil check // tagged adrp x10, _objc_debug_taggedpointer_classes@PAGE add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF ubfx x11, x0, #60, #4 ldr x16, [x10, x11, LSL #3] adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF cmp x10, x16 b.ne LGetIsaDone // ext tagged adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF ubfx x11, x0, #52, #8 ldr x16, [x10, x11, LSL #3] b LGetIsaDone // SUPPORT_TAGGED_POINTERS #endif LReturnZero: // x0 is already zero mov x1, #0 movi d0, #0 movi d1, #0 movi d2, #0 movi d3, #0 ret END_ENTRY _objc_msgSend
我们分析一下步骤:
objc_msgSend
方法的第一个参数receiver
是否为空taggedpointer
随后我们进入核心代码CacheLookup
//!!!!!!!!!重点!!!!!!!!!!!! .macro CacheLookup // // Restart protocol: // // As soon as we're past the LLookupStart$1 label we may have loaded // an invalid cache pointer or mask. // // When task_restartable_ranges_synchronize() is called, // (or when a signal hits us) before we're past LLookupEnd$1, // then our PC will be reset to LLookupRecover$1 which forcefully // jumps to the cache-miss codepath which have the following // requirements: // // GETIMP: // The cache-miss is just returning NULL (setting x0 to 0) // // NORMAL and LOOKUP: // - x0 contains the receiver // - x1 contains the selector // - x16 contains the isa // - other registers are set as per calling conventions // LLookupStart$1: //---- p1 = SEL, p16 = isa --- #define CACHE (2 * __SIZEOF_POINTER__),其中 __SIZEOF_POINTER__表示pointer的大小 ,即 2*8 = 16 //---- p11 = mask|buckets -- 从x16(即isa)中平移16字节,取出cache 存入p11寄存器 -- isa距离cache 正好16字节:isa(8字节)-superClass(8字节)-cache(mask高16位 + buckets低48位) ldr p11, [x16, #CACHE] //---- 64位真机 #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //--- p11(cache) & 0x0000ffffffffffff ,mask高16位抹零,得到buckets 存入p10寄存器-- 即去掉mask,留下buckets and p10, p11, #0x0000ffffffffffff // p10 = buckets //--- p11(cache)右移48位,得到mask(即p11 存储mask),mask & p1(msgSend的第二个参数 cmd-sel) ,得到sel-imp的下标index(即搜索下标) 存入p12(cache insert写入时的哈希下标计算是 通过 sel & mask,读取时也需要通过这种方式) and p12, p1, p11, LSR #48 // x12 = _cmd & mask //--- 非64位真机 #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 and p10, p11, #~0xf // p10 = buckets and p11, p11, #0xf // p11 = maskShift mov p12, #0xffff lsr p11, p12, p11 // p11 = mask = 0xffff >> p11 and p12, p1, p11 // x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64. #endif //--- p12是下标 p10是buckets数组首地址,下标 * 1<<4(即16) 得到实际内存的偏移量,通过buckets的首地址偏移,获取bucket存入p12寄存器 //--- LSL #(1+PTRSHIFT)-- 实际含义就是得到一个bucket占用的内存大小 -- 相当于mask = occupied -1-- _cmd & mask -- 取余数 add p12, p10, p12, LSL #(1+PTRSHIFT) // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) -- PTRSHIFT是3 //--- 从x12(即p12)中取出 bucket 分别将imp和sel 存入 p17(存储imp) 和 p9(存储sel) ldp p17, p9, [x12] // {imp, sel} = *bucket //--- 比较 sel 与 p1(传入的参数cmd) 1: cmp p9, p1 // if (bucket->sel != _cmd) //--- 如果不相等,即没有找到,请跳转至 2f b.ne 2f // scan more //--- 如果相等 即cacheHit 缓存命中,直接返回imp CacheHit $0 // call or return imp 2: // not hit: p12 = not-hit bucket //--- 如果一直都找不到, 因为是normal ,跳转至__objc_msgSend_uncached CheckMiss $0 // miss if bucket->sel == 0 //--- 判断p12(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素,),如果等于,则跳转至第3步 cmp p12, p10 // wrap if bucket == buckets //--- 定位到最后一个元素(即第一个bucket) b.eq 3f //--- 从x12(即p12 buckets首地址)- 实际需要平移的内存大小BUCKET_SIZE,得到得到第二个bucket元素,imp-sel分别存入p17-p9,即向前查找 ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket //--- 跳转至第1步,继续对比 sel 与 cmd b 1b // loop 3: // wrap: p12 = first bucket, w11 = mask #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //--- 人为设置到最后一个元素 //--- p11(mask)右移44位 相当于mask左移4位,直接定位到buckets的最后一个元素,缓存查找顺序是向前查找 add p12, p12, p11, LSR #(48 - (1+PTRSHIFT)) // p12 = buckets + (mask << 1+PTRSHIFT) #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 add p12, p12, p11, LSL #(1+PTRSHIFT) // p12 = buckets + (mask << 1+PTRSHIFT) #else #error Unsupported cache mask storage for ARM64. #endif // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. //--- 再查找一遍缓存() //--- 拿到x12(即p12)bucket中的 imp-sel 分别存入 p17-p9 ldp p17, p9, [x12] // {imp, sel} = *bucket //--- 比较 sel 与 p1(传入的参数cmd) 1: cmp p9, p1 // if (bucket->sel != _cmd) //--- 如果不相等,即走到第二步 b.ne 2f // scan more //--- 如果相等 即命中,直接返回imp CacheHit $0 // call or return imp 2: // not hit: p12 = not-hit bucket //--- 如果一直找不到,则CheckMiss CheckMiss $0 // miss if bucket->sel == 0 //--- 判断p12(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素)-- 表示前面已经没有了,但是还是没有找到 cmp p12, p10 // wrap if bucket == buckets b.eq 3f //如果等于,跳转至第3步 //--- 从x12(即p12 buckets首地址)- 实际需要平移的内存大小BUCKET_SIZE,得到得到第二个bucket元素,imp-sel分别存入p17-p9,即向前查找 ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket //--- 跳转至第1步,继续对比 sel 与 cmd b 1b // loop LLookupEnd$1: LLookupRecover$1: 3: // double wrap //--- 跳转至JumpMiss 因为是normal ,跳转至__objc_msgSend_uncached JumpMiss $0 .endmacro //以下是最后跳转的汇编函数 .macro CacheHit .if $0 == NORMAL TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp .elseif $0 == GETIMP mov p0, p17 cbz p0, 9f // don't ptrauth a nil imp AuthAndResignAsIMP x0, x12, x1, x16 // authenticate imp and re-sign as IMP 9: ret // return IMP .elseif $0 == LOOKUP // No nil check for ptrauth: the caller would crash anyway when they // jump to a nil IMP. We don't care if that jump also fails ptrauth. AuthAndResignAsIMP x17, x12, x1, x16 // authenticate imp and re-sign as IMP ret // return imp via x17 .else .abort oops .endif .endmacro .macro CheckMiss // miss if bucket->sel == 0 .if $0 == GETIMP //--- 如果为GETIMP ,则跳转至 LGetImpMiss cbz p9, LGetImpMiss .elseif $0 == NORMAL //--- 如果为NORMAL ,则跳转至 __objc_msgSend_uncached cbz p9, __objc_msgSend_uncached .elseif $0 == LOOKUP //--- 如果为LOOKUP ,则跳转至 __objc_msgLookup_uncached cbz p9, __objc_msgLookup_uncached .else .abort oops .endif .endmacro .macro JumpMiss .if $0 == GETIMP b LGetImpMiss .elseif $0 == NORMAL b __objc_msgSend_uncached .elseif $0 == LOOKUP b __objc_msgLookup_uncached .else .abort oops .endif .endmacro
检查消息接收者是否存在,为nil则不做任何处理
通过isa
指针找到对应的class
对象
找到class
类对象进行内存平移找到cache
从cache
中获取buckets
在buckets
中对比参数SEL
,查看缓存中有没有同名的方法
如果buckets中有对应的sel --> cacheHit --> 调用imp
如果在缓存中没有找到匹配的方法选择子,则执行慢速查找过程,即调用 _objc_msgSend_uncached
函数,并进一步调用 _lookUpImpOrForward
函数进行全局的方法查找。
总的来说:消息发送会先通过缓存进行查找方法实现,如果在缓存中没有找到方法实现,就会进入慢速查找过程,去类的方法列表以及父类链中进行循环查找
我们在上面的源码中提到了buckets
“bucket
” 是缓存的基本存储单位,通常包含了一个方法选择器 (SEL) 和一个对应的方法实现指针 (IMP)。当你向一个对象发送消息时,Objective-C 运行时会使用选择器(SEL)作为键,在缓存中查找对应的 bucket。如果找到了相应的 bucket,就可以直接获取到方法的 IMP 并执行,大大加快了方法调用的速度。
我们在上文说到如果我们在缓存中查找不到我们的方法,会进入__objc_msgSend_uncached
汇编函数
其核心是MethodTableLookup(即查询方法列表)
,其源码的核心是_lookUpImpOrForward
其是一个用C++实现的源码
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(); // Optimistic cache lookup // 优先尝试从缓存中获取方法实现,这是一个快速路径 if (fastpath(behavior & LOOKUP_CACHE)) { imp = cache_getImp(cls, sel); if (imp) goto done_nolock; // 如果找到,则直接跳到函数结束,避免锁操作 } // 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. // // TODO: this check is quite costly during process startup. // 校验给定的类对象是合法已注册的类,防止通过伪造对象进行攻击。 checkIsKnownClass(cls); // 如果类还未完全实现(realized),则进行实现,并保证锁在此过程中有效。 if (slowpath(!cls->isRealized())) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); // runtimeLock may have been dropped but is now locked again } // 如果需要初始化类,并且类还未初始化,则进行初始化。 if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); // runtimeLock may have been dropped but is now locked again // If sel == initialize, class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172 } runtimeLock.assertLocked(); curClass = cls; // The code used to lookpu 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();;) { // curClass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { imp = meth->imp; goto done; } // 如果没有找到实现,并且已经到了超类链的顶部,使用转发机制。 if (slowpath((curClass = curClass->superclass) == nil)) { // No implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; } // Halt if there is a cycle in the superclass chain. 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)) { // Found the method in a superclass. Cache it in this class. goto done; } } // No implementation found. Try method resolver once. if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); } done: log_and_fill_cache(cls, imp, sel, inst, curClass); runtimeLock.unlock(); done_nolock: if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; }
主要有以下几步:
cache
缓存中再次进行一次快速查找,防止多线程修改缓存,找到则直接返回,没找到就进入第二步cls
rw
for
循环,沿着superclass
继承链进行缓存与方法列表查找,具体步骤如下:cls
的方法列表中使用二分查找查找方法,如果找到就进入将其写入cache
并返回imp
如果快速查找与慢速查找都没有找到我们的方法,那么就会进入我们的动态方法解析
同样的我们首先查看动态解析的源码
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); //为什么要有这行代码? -- 类方法在元类中是对象方法,所以还是需要查询元类中对象方法的动态方法决议 if (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找 resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it //如果方法解析中将其实现指向其他方法,则继续走方法查找流程 return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE); }
主要分为以下几步
resolveInstanceMethod
resolveClassMethod
,如果在元类中没有找到或者为空,则在元类的实例方法的动态方法决议resolveInstanceMethod
中查找,主要是因为类方法在元类中是实例方法,所以还需要查找元类中实例方法的动态方法决议resolveInstanceMethod
/resolveClassMethod
这两个方法中将方法实现指向了其他方法,则继续调用lookUpImpOrForward
进行一次慢速查找老规矩还是看源码
static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); // look的是 resolveInstanceMethod --相当于是发送消息前的容错处理 if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel); //发送resolve_sel消息 // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls //查找say666 IMP imp = lookUpImpOrNil(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); } } }
主要分为以下步骤:
resolveInstanceMethod
消息前,需要查找cls类中是否有该方法的实现,即通过lookUpImpOrNil
方法又会进入lookUpImpOrForward
慢速查找流程查找resolveInstanceMethod
方法resolveInstanceMethod
消息lookUpImpOrNil
方法又会进入lookUpImpOrForward
慢速查找流程查找实例方法对于resolveClassMethod
实现不在过多赘述,与resolveInstanceMethod
相似
我们通过源码可以得知无论是类方法还是实例方法,最后都会来到resolveClassMethod
中,因此我们可以将实例方法 和 类方法的统一处理放在resolveInstanceMethod
方法中,如下所示:
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface MyClass : NSObject @end @implementation MyClass // 动态方法解析 + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(dynamicMethod)) { class_addMethod([self class], sel, (IMP)dynamicMethodImplementation, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } void dynamicMethodImplementation(id self, SEL _cmd) { NSLog(@"Dynamic method has been resolved and called."); } @end int main() { @autoreleasepool { MyClass *obj = [MyClass new]; // 调用未实现的方法,触发动态方法解析 [obj dynamicMethod]; } return 0; }
如果动态方法解析仍没有找到方法实现,那么就会进入消息转发中的快速转发(消息接受者替换),给开发者一个机会返回一个能够响应该方法的对象。该方法的签名如下:
- (id)forwardingTargetForSelector:(SEL)aSelector;
开发者可以在该方法中根据需要返回一个实现了该方法的对象,使得该对象能够接收并处理该消息。返回的对象会被用于接收消息,并执行对应的方法。如果返回nil,则进入下一步的消息转发机制。
通俗理解解释当前接收者无法处理消息,要将消息交给其他接收者处理,也就是指定一个B对象去处理A对象无法处理的消息
#import <Foundation/Foundation.h> // 定义备用接收者对象 @interface AnotherObject : NSObject - (void)anotherMethod; @end @implementation AnotherObject - (void)anotherMethod { NSLog(@"Method implemented in AnotherObject."); } @end // 主要对象 @interface MyClass : NSObject @end @implementation MyClass // 备用接收者 - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(anotherMethod)) { return [AnotherObject new]; } return [super forwardingTargetForSelector:aSelector]; } @end int main() { @autoreleasepool { MyClass *obj = [MyClass new]; // 调用未实现的方法,触发备用接收者 [obj performSelector:@selector(anotherMethod)]; } return 0; }
这一步骤中涉及到了两个方法
-methodSignatureForSelector
:: 当一个对象收到它无法识别的消息时,Objective-C
运行时首先调用这个方法以获取该消息对应方法的签名。这个方法签名是用于创建 NSInvocation
对象的基础,其中包括方法的返回类型、参数类型等信息。
-forwardInvocation:
: 一旦运行时通过-methodSignatureForSelector
: 获得了方法签名并创建了 NSInvocation
对象,它接着调用 -forwardInvocation:
。这个方法具体实现消息的转发过程,可以通过修改 NSInvocation 对象的属性(如目标对象和选择器)来控制消息如何被转发。
我们在NSInvocation
对象中既能选择方法选择器去替换,还能选择对象去替换
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// 如果当前类不响应某个方法,尝试从 SecondaryClass 获取方法签名
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [self.secondaryHandler methodSignatureForSelector:aSelector];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
anInvocation.selector = @selector(handleUnrecognizedMessage:);
//选择一个消息接收者(对象)去替换
anInvocation.target = self.secondaryHandler;
[anInvocation invoke];
}
这里需要注意的是一定需要实现methodSignatureForSelector:
方法之后返回NSInvocation *
对象再去调用forwardInvocation
才能算完成完整的消息转发,单单调用forwardInvocation
会造成报错
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。