主要探究 objc_msgSend()的流程;
文中使用的 objc4源碼是objc-781版本;
runtime 部分一
runtime 部分二
runtime 部分三
1. objc_msgSend()的流程;
首先我們都知道, 我們調(diào)用方法后到底層就是通過(guò)objc_msgSend()
來(lái)實(shí)現(xiàn)的, 這個(gè)機(jī)制就是我們所說(shuō)的消息發(fā)送機(jī)制, 這個(gè)過(guò)程分為消息發(fā)送
動(dòng)態(tài)解析
消息轉(zhuǎn)發(fā)
三個(gè)階段;
轉(zhuǎn)化為objc_msgSend()
有兩個(gè)參數(shù), 第一個(gè)是接收者receiver
, 第二個(gè)是SEL
;
///不論是類方法還是實(shí)例方法底層都是objc_msgSend()方式實(shí)現(xiàn);
Person *person = [[Person alloc] init];
[person realizedMehod];
///轉(zhuǎn)化為C++后代碼如下
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("realizedMehod"));
由于 objc_msgSend()
的實(shí)現(xiàn)過(guò)程是通過(guò)匯編實(shí)現(xiàn), 本人能力有限, 只能大致推測(cè)下, 從 objc_msgSend()
開(kāi)始的整個(gè)流程, 如果有;理解錯(cuò)誤的地方還請(qǐng)不吝賜教;
注意匯編中調(diào)用 C
或者 C++
的函數(shù)會(huì)在函數(shù)名之前加上_
, 例如objc_msgSend()
在匯編中對(duì)應(yīng)的就是_objc_msgSend
;
///入口
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd, ...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in x17
* x16 reserved for our use but not used
*
********************************************************************/
....
//_objc_msgSend開(kāi)始
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
///在 arm64架構(gòu)下SUPPORT_TAGGED_POINTERS = 1 , 所以下一步LNilOrTagged, 檢查 receiver 是否為空;
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
///通過(guò) isa 獲取 class
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
///開(kāi)始緩存查找, 注意入?yún)⑹荖ORMAL
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
===>
///緩存查找
.macro CacheLookup
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
/*
arm64架構(gòu)執(zhí)行這里的邏輯;
雖然我們不知道匯編的具體作用, 但是從注釋依然可以看出一些信息. 從散列表(buckets)中
通過(guò)_cmd & mask來(lái)獲取方法 IMP, 這個(gè)跟之前的方法緩存的算法和過(guò)程相對(duì)應(yīng);
*/
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#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
...
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
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.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
///找到緩存 call or return imp
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
///緩存沒(méi)有找到, 調(diào)用CheckMiss
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
===>
///宏定義CheckMiss
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
///之前的入?yún)⑹荖ORMAL, 下一步調(diào)用__objc_msgSend_uncached
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
===>
///__objc_msgSend_uncached的流程
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
///調(diào)用MethodTableLookup
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
===>
.macro MethodTableLookup
...
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
///調(diào)用lookUpImpOrForward方法
bl _lookUpImpOrForward
...
下面就是到源碼lookUpImpOrForward的實(shí)現(xiàn);
===>
//**********************************************************************************************//
在arm64-asm.h文件中可以得知在 arm64 & __LP64__模式下SUPPORT_TAGGED_POINTERS = 1
#if __arm64__
#if __LP64__
// true arm64
#define SUPPORT_TAGGED_POINTERS 1
....
===
在objc-config.h文件中我們可以得知在 arm64 & __LP64__模式下 CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_1
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
#endif
//**********************************************************************************************//
上面的過(guò)程主要是記錄下在匯編層面objc_msgSend()
的流程, 下面著重說(shuō)下lookUpImpOrForward
的實(shí)現(xiàn);
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
標(biāo)準(zhǔn)的 IMP 查找方法
...
* If you don't want forwarding at all, use LOOKUP_NIL.
注意這句話, 如果你完全不想使用消息轉(zhuǎn)發(fā), 則使用LOOKUP_NIL
**********************************************************************/
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
///獲取消息轉(zhuǎn)發(fā)的 IMP
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
///初始的imp為空
IMP imp = nil;
///當(dāng)前類
Class curClass;
///runtime鎖
runtimeLock.assertUnlocked();
// Optimistic cache lookup
///查找方法緩存, 如果查找到緩存直接結(jié)束流程將方法返回
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
///檢查類的initialize相關(guān)
runtimeLock.lock();
checkIsKnownClass(cls);
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.assertLocked();
curClass = cls;
///核心方法, 通過(guò)向上遍歷當(dāng)前類的父類, 來(lái)查找imp
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
///獲取當(dāng)前類的方法列表, 不查詢父類
Method meth = getMethodNoSuper_nolock(curClass, sel);
///如果當(dāng)前類中查找到imp則結(jié)束查找 去done:出緩存imp然后返回這個(gè)imp;
if (meth) {
imp = meth->imp;
goto done;
}
/*
注意這個(gè)地方的寫法, 執(zhí)行完這句代碼后, curClass就已經(jīng)指向父類了;
if (slowpath((curClass = curClass->superclass) == nil)) {}
等同于下面兩句語(yǔ)句組合, 如果有疑問(wèn)可以自行測(cè)試下;
curClass = curClass->superclass
if (slowpath(curClass == nil) {}
如果查詢到基類仍然沒(méi)有查找大相關(guān)方法, 則使用消息轉(zhuǎn)發(fā)(注意這里只是跳出循環(huán))
實(shí)際上下面要先判斷動(dòng)態(tài)解析, 動(dòng)態(tài)解析是先于消息轉(zhuǎn)發(fā)的;
*/
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.
/*
從curClass已經(jīng)指向了父類, 所以這里判斷父類中是否有消息轉(zhuǎn)發(fā),
如果子類沒(méi)有消息轉(zhuǎn)發(fā)相關(guān)處理, 寫在父類中實(shí)現(xiàn)消息轉(zhuǎn)發(fā)也會(huì)有效;
仍然是跳出循環(huán), 先去動(dòng)態(tài)解析;
*/
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;
}
//如果父類中方法緩存中查找到了, 則將方法緩存到本類然后返回imp
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
/*
如果沒(méi)有找到IMP則開(kāi)始動(dòng)態(tài)解析, 不論是否成功最后都會(huì)再次調(diào)用lookUpImpOrForward;
注意只進(jìn)行一次動(dòng)態(tài)解析, 如果動(dòng)態(tài)解析成功, 則將相關(guān)方法緩存到本類;
下次再次調(diào)用時(shí)則是直接查找類中方法列表即可;
如果動(dòng)態(tài)解析失敗, 則再次lookUpImpOrForward, 重新開(kāi)始流程, 進(jìn)入消息轉(zhuǎn)發(fā)階段;
具體請(qǐng)看resolveMethod_locked的官方注釋和實(shí)現(xiàn)流程;
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
///將查找到的imp緩存
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
///如果完全不想使用消息轉(zhuǎn)發(fā), 但是獲取到的緩存imp=forward_imp則直接返回nil
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
///最終返回imp
return imp;
}
//******************************************************************//
///動(dòng)態(tài)解析方法入口
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);
}
===>
///以實(shí)例方法為例, 動(dòng)態(tài)解析方法入口中會(huì)調(diào)用 resolveInstanceMethod方法
/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
///尋找要添加到類中的method;
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls) {
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(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);
// Cache the result (good or bad) so the resolver doesn't fire next time.
//不論好壞將結(jié)果緩存起來(lái), 動(dòng)態(tài)解析不會(huì)再觸發(fā)
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
...
}
至此objc_msgSend()
的流程大致結(jié)束, 下面代碼測(cè)試下動(dòng)態(tài)解析和消息轉(zhuǎn)發(fā);
2. 動(dòng)態(tài)解析
兩個(gè)關(guān)鍵方法: 實(shí)例方法+(BOOL)resolveInstanceMethod:
; 類方法+(BOOL)resolveClassMethod:
以實(shí)例方法為例, 簡(jiǎn)單的理解為:
調(diào)用notRealizeMethod
方法后后, 如果本類或者父類實(shí)現(xiàn)了這個(gè)方法則調(diào)用, 否則檢查是否實(shí)現(xiàn)動(dòng)態(tài)解析方法,
實(shí)現(xiàn)了resolveInstanceMethod
方法,開(kāi)始動(dòng)態(tài)解析; 這個(gè)地方就是動(dòng)態(tài)為本類添加一個(gè)方法去映射實(shí)現(xiàn)notRealizeMethod
;
沒(méi)有實(shí)現(xiàn)resolveInstanceMethod
, 程序crash
拋出unrecognized selector sent to instance;
開(kāi)始動(dòng)態(tài)解析;
調(diào)用未實(shí)現(xiàn)的方法notRealizeMethod
Cat *cat = [[Cat alloc] init];
[cat notRealizeMethod];
///Cat的實(shí)現(xiàn)如下;
//.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Cat : NSObject
///此方法未實(shí)現(xiàn)
- (void)notRealizeMethod;
@end
NS_ASSUME_NONNULL_END
///.m文件為
#import "Cat.h"
#import <objc/runtime.h>
@implementation Cat
- (void)HandleNotRealizedMethod {
NSLog(@"HandleNotRealizedMethod : %s", __func__);
}
/*
對(duì)notRealizeMethod方法, 沒(méi)有實(shí)現(xiàn), 調(diào)用后;
實(shí)現(xiàn)了resolveInstanceMethod方法,開(kāi)始動(dòng)態(tài)解析;
沒(méi)有實(shí)現(xiàn)resolveInstanceMethod, 程序crash拋出unrecognized selector sent to instance;
開(kāi)始動(dòng)態(tài)解析
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(notRealizeMethod)) {
SEL handelSel = @selector(HandleNotRealizedMethod);
Method handleMethod = class_getInstanceMethod(self, handelSel);
IMP imp = class_getMethodImplementation(self, handelSel);
/*
為某個(gè)類添加Method;
參數(shù)1: 為哪個(gè)類添加方法;
參數(shù)2: 為哪個(gè)方法添加實(shí)現(xiàn);
參數(shù)3: 方法的具體實(shí)現(xiàn);
參數(shù)4: 方法的編碼格式;
*/
class_addMethod(self, sel, imp, method_getTypeEncoding(handleMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
///運(yùn)行后的結(jié)果為
2020-07-04 17:36:59.568045+0800 objc_msgSend[11787:194063] HandleNotRealizedMethod : -[Cat HandleNotRealizedMethod]
3. 消息轉(zhuǎn)發(fā)
如果動(dòng)態(tài)解析方法沒(méi)有實(shí)現(xiàn), 或者沒(méi)有處理動(dòng)態(tài)解析, 則進(jìn)入消息轉(zhuǎn)發(fā)階段;
以實(shí)例方法為例, 大致流程為:
幾個(gè)主要的方法;
- (id)forwardingTargetForSelector:()
返回一個(gè)能處理notRealizeInstanceMethod的對(duì)象;
- (NSMethodSignature *)methodSignatureForSelector:()
方法簽名;
- (void)forwardInvocation:()
最終的處理
注意: 類方法也有消息轉(zhuǎn)發(fā);
把相關(guān)的方法打出后手動(dòng)改為+
即可;處理的流程跟實(shí)例方法類似;
+ (id)forwardingTargetForSelector:()
;
+ (NSMethodSignature *)methodSignatureForSelector:()
;
+ (void)forwardInvocation:()
;
測(cè)試代碼
///調(diào)用方法
Pig *pig = [[Pig alloc] init];
[pig notRealizeInstanceMethod];
[Pig notRealizeClassMethod];
///動(dòng)態(tài)解析階段, 不處理或者處理不成功, 進(jìn)入消息轉(zhuǎn)發(fā)階段
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return [super resolveInstanceMethod:sel];
}
/*
aSelector這個(gè)時(shí)候就是notRealizeInstanceMethod;
這個(gè)地方需要返回一個(gè)值, 就是返回一個(gè)能處理notRealizeInstanceMethod的對(duì)象;
例如Piggy也有實(shí)例方法notRealizeInstanceMethod, 并且也實(shí)現(xiàn)了,這時(shí)可以返回Piggy的實(shí)例對(duì)象;即可處理方法;
*/
- (id)forwardingTargetForSelector:(SEL)aSelector {
//如過(guò)將下面注釋的代碼打開(kāi), 則直接將消息轉(zhuǎn)發(fā)給Piggy的實(shí)例對(duì)象, 方法簽名的相關(guān)方法不再生效;
///如果發(fā)現(xiàn)Pig對(duì)象調(diào)用notRealizeInstanceMethod方法, 方法沒(méi)有實(shí)現(xiàn), 并且動(dòng)態(tài)解析失敗, 則將消息轉(zhuǎn)發(fā)給Piggy對(duì)象;
// if (aSelector == @selector(notRealizeInstanceMethod)) {
// Piggy *piggy = [[Piggy alloc] init];
// /*
// 消息轉(zhuǎn)發(fā)的過(guò)程, 源碼不開(kāi)源, 并不能找到相關(guān)流程, 但是消息轉(zhuǎn)發(fā)后有做一個(gè)操作就是objc_msgSend( piggy, aSelector);
// 就是讓piggy調(diào)用aSelector;
// */
// return piggy;
// }
return [super forwardingTargetForSelector:aSelector];
}
/*
如果forwardingTargetForSelector并不能返回一個(gè)有效的對(duì)象; 開(kāi)始進(jìn)入方法簽名階段
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
/*
方法編碼, 要返回aSelector的編碼格式;
如果返回一個(gè)合理的值, 則調(diào)用forwardInvocation方法;
*/
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
return sig;
}
/*
到了這一步, 可以做任何操作了
類似KVC是實(shí)現(xiàn)了setValue: forUndefinedKey:即使沒(méi)有相應(yīng)key, 實(shí)現(xiàn)了此方法, 什么都不做也不會(huì)崩潰;
NSInvocation封裝了一個(gè)方法的調(diào)用信息; 調(diào)用者, 方法, 方法編碼;
target: 方法之前的調(diào)用者, 可更改為其他調(diào)用者;
selector : 需要調(diào)用的方法, 可更改為其他方法;
methodSignature : 方法的簽名信息; 不可更改;
只要target和selector是配套合理的,methodSignature可以忽略;例如:
anInvocation.target = [Dog class];
anInvocation.selector = @selector(classTest:);
[anInvocation invoke];
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
anInvocation.target = [Dog class];
anInvocation.selector = @selector(classTest:);
[anInvocation invoke];
}
以類方法為例驗(yàn)證另一個(gè)問(wèn)題: 子類中調(diào)用沒(méi)有實(shí)現(xiàn)方法, 且沒(méi)有做方法的消息轉(zhuǎn)發(fā), 但是父類實(shí)現(xiàn)了消息轉(zhuǎn)發(fā), 也會(huì)有效;
//調(diào)用方法
[Pig notRealizeClassMethod];
///Pig的父類Animal的.m實(shí)現(xiàn)為
#import "Animal.h"
@implementation Animal
+ (id)forwardingTargetForSelector:(SEL)aSelector {
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
return sig;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"Pig類中沒(méi)有實(shí)現(xiàn)notRealizeClassMethod方法, 且沒(méi)有做類方法的消息轉(zhuǎn)發(fā), 但是父類實(shí)現(xiàn)了消息轉(zhuǎn)發(fā), 也會(huì)有效;");
}
@end
2020-07-04 18:16:20.022633+0800 objc_msgSend[15196:261753] Pig類中沒(méi)有實(shí)現(xiàn)notRealizeClassMethod方法,
且沒(méi)有做類方法的消息轉(zhuǎn)發(fā), 但是父類實(shí)現(xiàn)了消息轉(zhuǎn)發(fā), 也會(huì)有效;
4. 流程圖總結(jié)消息發(fā)送, 動(dòng)態(tài)解析, 消息轉(zhuǎn)發(fā)的過(guò)程
-
消息發(fā)送階段的流程:
-
動(dòng)態(tài)解析階段流程:
image.png -
消息轉(zhuǎn)發(fā)流程
類方法的處理流程類似, 把相關(guān)的-
號(hào)方法換成+
號(hào)方法;
參考文章和下載鏈接
Apple 一些源碼的下載地址
方法的查找順序
什么是散列表
LP64 結(jié)構(gòu)數(shù)據(jù)占據(jù)多少位
LP64什么意思
匯編和 C 函數(shù)的相互調(diào)用
iOS 方法簽名機(jī)制
iOS方法返回值和參數(shù)對(duì)應(yīng)的Type Encodings