寫在前面
在OC里面,調(diào)用對象的某個方法其實就是給這個對象發(fā)送一個消息,這個過程我們把它分為三大階段蔬胯,分別為:消息發(fā)送階段、動態(tài)解析階段位他、消息轉(zhuǎn)發(fā)階段氛濒,本文將細細剖析這三個階段产场,但是在剖析這三大階段之前我們需要先回顧一下Class的結(jié)構(gòu)。
Class結(jié)構(gòu)
蘋果源碼最新下載地址請點擊:蘋果源碼
在objc-runtime-new.h
中可以看到objc_class
結(jié)構(gòu)如下:
struct objc_object {
Class isa;
};
struct objc_class : objc_object {
Class superclass;
cache_t cache; // 方法緩存
class_data_bits_t bits; // 獲取具體類信息
class_rw_t *data() const {
return bits.data();
}
......
};
從上面的結(jié)構(gòu)我們可以看到有一個類cache_t
舞竿,這個類就是專門拿來做方法緩存相關(guān)的類京景,結(jié)構(gòu)如下:
struct cache_t {
struct bucket_t *buckets();
mask_t occupied();
mask_t mask();
};
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
class_data_bits_t
用于獲取具體的類信息,結(jié)構(gòu)如下:
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
// readWrite:可讀可寫
struct class_rw_t {
uint32_t flags;
uint32_t witness;
Class firstSubclass;
Class nextSiblingClass;
class_rw_ext_t *ext;
const class_ro_t *ro;
};
struct class_rw_ext_t {
class_ro_t *ro;
method_array_t methods;// 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 協(xié)議列表
char *demangledName;
uint32_t version;
}
// readOnly:只讀
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 類名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
分析到這里骗奖,Class
結(jié)構(gòu)我們已了解清楚确徙,接下來就是調(diào)用對象的方法來研究一下消息發(fā)送的完整流程。
消息發(fā)送階段
在OC里面执桌,調(diào)用對象的某個方法就是給這個對象發(fā)送一條消息鄙皇,這里我們新建一個Person類,以[person personRun]為例來看看消息發(fā)送階段的流程仰挣。
在【iOS重學】方法緩存cache_t的分析這篇文章中我們主要分析了方法緩存育苟,建議大家先看一下緩存可以幫助我們理解接下來的流程。
我們知道OC中的方法調(diào)用其實就是轉(zhuǎn)成objc_msgSend()
函數(shù)的調(diào)用(load方法除外)椎木,如下:
// 消息發(fā)送階段源碼跟讀順序
1. objc-msg-arm64 匯編文件
ENTRY _objc_msgSend
b.le LNilOrTagged
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
.macro CacheLookup
CacheHit // 命中緩存
MissLabelDynamic // 其實就是__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
MethodTableLookup
.macro MethodTableLookup
bl _lookupImpOrForward
2. objc-runtime-new.mm 文件
lookupImpOrForward
getMethodNoSuper_nolock(curClass, sel)
curClass = curClass->getSuperclass()
cache_getImp(curClass, sel) // 從父類緩存里面查找
log_and_fill_cache // 緩存方法到消息接收者這個類
消息發(fā)送的流程圖如下:
我們來驗證一下是否真的緩存了調(diào)用的方法:
未調(diào)用personRun時违柏,我們查一下在Person類的cache里面是否能找到personRun方法緩存:
Person *person = [[Person alloc] init]; mj_objc_class *personClass = (__bridge mj_objc_class *)[Person class]; NSLog(@"%@ %p",NSStringFromSelector(@selector(personRun)), personClass->cache.imp(@selector(personRun)));
打印結(jié)果如下:
2022-04-10 13:11:30.367394+0800 RuntimeDemo[88049:12459843] personRun 0x0
結(jié)果分析:在cache并沒有找到personRun的IMP。
調(diào)用personRun之后香椎,我們查一下Person類的cache里面是否能找到personRun方法緩存:Person *person = [[Person alloc] init]; [person personRun]; mj_objc_class *personClass = (__bridge mj_objc_class *)[Person class]; NSLog(@"%@ %p",NSStringFromSelector(@selector(personRun)), personClass->cache.imp(@selector(personRun)));
打印結(jié)果如下:
2022-04-10 13:13:30.294687+0800 RuntimeDemo[88074:12461806] personRun 0x78cc0
結(jié)果分析:調(diào)用personRun之后漱竖,會把personRun緩存到方法緩存里面
動態(tài)方法解析階段
當?shù)谝浑A段【消息發(fā)送階段】沒有找到方法實現(xiàn)就會進入第二階段【動態(tài)方法解析階段】。
// 動態(tài)方法解析階段源碼跟讀順序
1. objc-runtime-new.mm 文件
resolveMethod_locked
resolveInstanceMethod 或 resolveClassMethod
lookupImpOrNilTryCache
_lookupImpTryCache
lookupImpOrForward
動態(tài)方法解析的流程圖如下:
動態(tài)方法解析流程
根據(jù)+ (BOOL)resolveInstanceMethod:(SEL)sel (實例方法調(diào)用這個)
或+ (BOOL)resolveClassMethod:(SEL)sel(類方法調(diào)用這個)
來做動態(tài)方法解析畜伐,然后重新走一遍消息發(fā)送的流程(從消息接受者的方法緩存里面開始繼續(xù)往下執(zhí)行)
動態(tài)方法解析代碼
- (void)otherRun {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(personRun)) {
Method otherMethod = class_getInstanceMethod(self, @selector(otherRun));
IMP imp = class_getMethodImplementation(self, @selector(otherRun));
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
消息轉(zhuǎn)發(fā)階段
如果前面的兩個階段都沒有實現(xiàn)馍惹,就會繼續(xù)進入【消息轉(zhuǎn)發(fā)】的流程。
// 消息轉(zhuǎn)發(fā)階段的源碼跟讀順序
1. objc-msg-arm64 匯編文件
forward_imp = _objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
b __objc_msgForward
ENTRY __objc_msgForward
ENTRY __objc_msgForward_stret
__objc_forward_stret_handler
2. objc-runtime-new.mm 文件
void *_objc_forward_stret_handler = (void *)objc_defaultForwardStretHandler;
3. CoreFoundation 框架
__forwarding__ // 不開源
消息轉(zhuǎn)發(fā)的流程圖如下:
消息轉(zhuǎn)發(fā)流程
消息轉(zhuǎn)發(fā)流程也分為了兩步:
第一步:forwardingTargetForSelector:
方法是指把響應(yīng)這個方法的對象轉(zhuǎn)發(fā)給其他的對象玛界,那么消息接受者就發(fā)生了變化万矾,會重新調(diào)用一遍objc_MsgSend(消息接受者,SEL)
流程
第二步:forwardingTargetForSelector:
方法返回為nil
慎框,繼續(xù)檢查methodSignatureForSelector:
是否返回了一個方法簽名良狈,然后去執(zhí)行forwardInvocation:
方法
消息轉(zhuǎn)發(fā)流程相關(guān)代碼實現(xiàn)
- 實例方法流程
// 第一步
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(personRun)) {
return [[Student alloc] init]; // 這里返回的是你想把這個消息轉(zhuǎn)發(fā)給哪個對象
}
return [super forwardingTargetForSelector:aSelector];
}
// 第二步
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(personRun)) {
// ??:這里的方法簽名的types不能隨便寫 因為這里的方法簽名決定了下一步的NSInvocation的返回值、參數(shù)類型等
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[Student new]];
// 在這個方法里可以做任何我們想做的事情
}
類方法流程
// 第一步
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(personTest)) {
return [Student class];
}
return [super forwardingTargetForSelector:aSelector];
}
// 第二步
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(personTest)) {
return [Student methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[Student class]];
}
+ (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"%s",__func__);
}
關(guān)于NSInvocation
類
@interface NSInvocation : NSObject
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
// 方法簽名
@property (readonly, retain) NSMethodSignature *methodSignature;
// retain所有參數(shù) 防止參數(shù)被dealloc
- (void)retainArguments;
// 參數(shù)是否都被retained
@property (readonly) BOOL argumentsRetained;
// 消息接收者
@property (nullable, assign) id target;
// 方法名
@property SEL selector;
// 獲取返回值
- (void)getReturnValue:(void *)retLoc;
// 設(shè)置返回值
- (void)setReturnValue:(void *)retLoc;
// 獲取idx的參數(shù)
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
// 設(shè)置idx的參數(shù)
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
// 調(diào)用
- (void)invoke;
- (void)invokeWithTarget:(id)target;
@end
大家有興趣的話可以去試試NSInvacation的使用笨枯。
最后
如果按照上面的三大流程都走完之后依然沒有找到相應(yīng)的方法實現(xiàn)薪丁,那這個調(diào)用最后就會調(diào)用doesNotRecognizeSelecto:
拋出異常,如果錯誤請多多指教馅精,最后歡迎去我的個人技術(shù)博客逛逛严嗜。