引言
消息轉(zhuǎn)發(fā)
的本質(zhì):向對(duì)象
發(fā)送消息,是一個(gè)查找方法
的過(guò)程。在前面我們研究過(guò)類(lèi)
,編譯成c++
本質(zhì)是一個(gè)叫objc_class
的struct指針
,
objc_class
里面有一個(gè)重要的成員cache_t
障癌,用于方法
的緩存。
查找方法的過(guò)程
在底層即是一個(gè)根據(jù)sel
找到imp
的流程辩尊。
這里在探究消息轉(zhuǎn)發(fā)概念之前涛浙,不得不提到一個(gè)在Objective-C
里面一個(gè)很重要的概念:runtime
。
-
運(yùn)行時(shí)
是相對(duì)于編譯時(shí)
來(lái)說(shuō)的 -
編譯時(shí)
摄欲,主要是做一些詞法語(yǔ)法分析轿亮,也就是編譯時(shí)類(lèi)型檢查
,編譯時(shí)刻代碼是不會(huì)被加載到內(nèi)存里
胸墙。 -
運(yùn)行時(shí)
:可執(zhí)行文件被裝載到內(nèi)存
中我注,代碼跑起來(lái)了,會(huì)做運(yùn)行時(shí)檢查
迟隅,但是和編譯時(shí)
的檢查又不一樣
但骨,不是簡(jiǎn)單的掃描代碼,而是在 內(nèi)存中做些操作智袭,做些判斷奔缠。
舉例如下:
創(chuàng)建兩個(gè)類(lèi)WJPerson
和WJTeacher
,并且WJTeacher 繼承于WJPerson
吼野。
WJTeacher:
#import <Foundation/Foundation.h>
#import "WJPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface WJTeacher : WJPerson
//聲明并實(shí)現(xiàn)
- (void)playFootball;
//只聲明不實(shí)現(xiàn)校哎,在父類(lèi)實(shí)現(xiàn)
- (void)playBasketball;
@end
NS_ASSUME_NONNULL_END
#import "WJTeacher.h"
@implementation WJTeacher
- (void)playFootball{
NSLog(@"%s",__func__);
}
@end
WJPerson:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WJPerson : NSObject
//這里方法沒(méi)帶參數(shù),有興趣的可以把帶參數(shù)的試試
- (void)playBasketball;
@end
NS_ASSUME_NONNULL_END
#import "WJPerson.h"
@implementation WJPerson
- (void)playBasketball{
NSLog(@"%s",__func__);
}
@end
在main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
WJTeacher *p = [WJTeacher alloc];
[p playFootball];
[p playBasketball];
}
return 0;
}
2021-07-01 16:43:14.786334+0800 KCObjcBuild[1925:49713] -[WJTeacher playFootball]
2021-07-01 16:43:14.786870+0800 KCObjcBuild[1925:49713] -[WJPerson playBasketball]
分析如上代碼
:在WJTeacher
我們只是聲明
了playBasketball
方法瞳步,但是控制臺(tái)
仍然顯示該方法調(diào)用成功
闷哆。為什么會(huì)這樣腰奋?
首先:我們還是clang
下這段代碼,看看編譯
后到底是什么樣子阳准。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
WJTeacher *p = ((WJTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("WJTeacher"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("playFootball"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("playBasketball"));
//帶參數(shù)的-(void)playWithWhom:(NSString*)name;
Dog*dog = ((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Dog"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)dog, sel_registerName("playWithWhom:"), (NSString *)&__NSConstantStringImpl__var_folders_4c_6y4z_qs972qgqg7g06frb7540000gn_T_main_48f0a4_mi_0);
}
return 0;
}
從而引出了我們所要研究的主題:objc_msgSend
objc_msgSend
這里發(fā)現(xiàn)氛堕,
objc_msgSend(void /* id self, SEL op, ... */ )
方法雖然出來(lái)了馏臭,但是參數(shù)
為空野蝇,需要進(jìn)行一步設(shè)置
結(jié)論:[p playFootball]
等價(jià)objc_msgSend(p,sel_registerName("playFootball"))
。