Runtime之objc_msgSend執(zhí)行流程

1晒骇、objc_msgSend本質(zhì)

在OC中霸褒,方法調(diào)用其實就是轉(zhuǎn)換成objc_msgSend函數(shù)的調(diào)用仓犬。
發(fā)送message只需要指定 對象 和 SEL 侠驯,Runtime的objc_msgSend會根據(jù)信息在對象isa指針指向的Class中尋找該SEL對應的IMP,從而完成方法的調(diào)用镐侯。

MJPerson *peron = [[MJPerson alloc] init];
    
[peron personTest];
//編譯后.cpp文件:
//((void (*)(id, SEL))(void *)objc_msgSend)((id)peron, sel_registerName("personTest"));
//消息接受者(receiver):  peron
//消息名稱:  “personTest”
    
    
[MJPerson initialize];
//編譯后.cpp文件:
// ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("initialize"));
//消息接受者(receiver):  objc_getClass("MJPerson"),  是MJPerson這個類侦讨,而不是實例對象
//消息名稱:  initialize
    
    
NSLog(@"%p",sel_registerName("personTest"));
NSLog(@"%p",@selector(personTest));
//以上兩句打印的地址是一樣的,這說明方法名一樣苟翻,就是一個東西
//只是編譯器把iOS的@selector()轉(zhuǎn)成了C的sel_registerName()這樣的方法

objc_msgSend的執(zhí)行流程可以分為3大階段:
消息發(fā)送 -> 動態(tài)方法解析 -> 消息轉(zhuǎn)發(fā)
下面韵卤,我們來詳細說明一下:

2、消息發(fā)送
消息發(fā)送流程
  • 檢查receiver是否有效崇猫,有效則開始查找方法沈条;
  • 先從receiverClass的cache中查找方法,找得到就跳到對應的函數(shù)去執(zhí)行诅炉,如果cache中找不到就去receiverClass的class_rw_t中去查找蜡歹;
  • 如果還是找不到就依次去超類的cache中、class_rw_t中查找涕烧,直到找到NSObject類為止
  • 如果還找不到就進入動態(tài)方法解析月而。
3、動態(tài)方法解析
動態(tài)方法解析流程
#import "MJPerson.h"
#import <objc/runtime.h>


//method_t的結(jié)構(gòu)如下
//可以利用method_t來定義方法议纯,并且otherMethod->imp父款、otherMethod->types的方式取得imp、types、name
struct method_t {
    SEL name;
    char *types;
    IMP imp;
};

@implementation MJPerson

//方法1:使用結(jié)構(gòu)體method_t來定義方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personTest)) {
        //獲取其他方法
        struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
       //動態(tài)添加方法的實現(xiàn)
        class_addMethod(self, sel, otherMethod->imp , otherMethod->types);
        //返回YES
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}



//方法2:使用Method來定義方法憨攒,需要通過API取得方法的imp世杀、types、name
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personTest)) {
        //動態(tài)添加方法的實現(xiàn)
        Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
        //動態(tài)添加test方法的實現(xiàn)
        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}



//方法3:使用C語言函數(shù)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personTest)) {
        //動態(tài)添加方法的實現(xiàn)
        class_addMethod(self, sel, (IMP)c_otherTest, "v16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

-(void)otherTest{
    NSLog(@"Person otherTest");
}

void c_otherTest(id self,SEL _cmd){
    NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
@end

上面代碼中的resolveInstanceMethod是用來動態(tài)解析實例方法的肝集,
類方法是用resolveClassMethod來動態(tài)解析的玫坛。
對于類方法,基本和實例方法一樣包晰,只是需要實現(xiàn)resolveClassMethod方法;另外在class_addMethod中需要用object_getClass(self)取得類對象炕吸,對類對象進行添加方法操作伐憾;

struct method_t {
    SEL name;
    char *types;
    IMP imp;
};

//方法1:使用結(jié)構(gòu)體method_t來定義方法
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(PersonClassTest)) {
        //獲取其他方法
        struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
        
        //因為是MJPerosn這個類去調(diào)用,要用object_getClass(self)
        class_addMethod(object_getClass(self), sel, otherMethod->imp , otherMethod->types);
        //返回YES
        return YES;
    }
    return [super resolveClassMethod:sel];
}


//方法2:使用Method來定義方法赫模,需要通過API取得方法的imp树肃、types、name
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(PersonClassTest)) {
        Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
       
        //因為是MJPerosn這個類去調(diào)用瀑罗,要用object_getClass(self)
        class_addMethod(object_getClass(self), sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


//方法3:使用C語言函數(shù)
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(PersonClassTest)) {
        //因為是MJPerosn這個類去調(diào)用胸嘴,要用object_getClass(self)
        class_addMethod(object_getClass(self), sel, (IMP)c_otherTest, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}


-(void)otherTest{
    NSLog(@"Person cl otherTest");
}


void c_otherTest(id self,SEL _cmd){
    NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
  • 如果事先沒有動態(tài)解析,那便會通過resolveInstanceMethod / resolveClassMethod方法斩祭,對receiver動態(tài)添加方法劣像,然后再進入消息發(fā)送;
  • 如果之前已經(jīng)有動態(tài)方法解析了摧玫,那進入消息轉(zhuǎn)發(fā)耳奕。
4、消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)流程

forwardingTargetForSelector方法

//程序中調(diào)用personTest
MJPerson *person = [[MJPerson alloc] init];
[person personTest];


//在person.m文件中實現(xiàn):
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(personTest)) {
        //返回MJCat诬像,要求在MJCat中實現(xiàn)personTest方法
        //就相當于調(diào)用了objc_msgSend([[MJCat alloc] init],aSelector);
        return [[MJCat alloc] init];
        
        //通過建立分類屋群,在NSObject分類中實現(xiàn)personTest方法,也可以成功實現(xiàn)消息轉(zhuǎn)發(fā)
        //return [[NSObject alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

消息轉(zhuǎn)發(fā)坏挠,如果forwardingTargetForSelector這個方法沒有實現(xiàn) 或者 這個方法return nil的話芍躏。這種情況下,會調(diào)用methodSignatureForSelector方法

#pragma mark -無參的無返回值的可以用v@:  不一定非要寫數(shù)字

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(personTest)) {
        //返回方法簽名:包含返回值類型降狠、參數(shù)類型
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
       
        //也可以使用下面這句代替对竣,這樣就不用自己管Type Encodings了
        //return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

//    NSInvocation封裝了一個方法調(diào)用:包含方法調(diào)用者、方法名喊熟、方法參數(shù)
//    anInvocation.target   --->  方法調(diào)用者
//    anInvocation.selector   --->   方法名字
//    [anInvocation getArgument:NULL atIndex:0]  --->  方法參數(shù)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"可以在這個方法中實現(xiàn)你想做的操作");
    [anInvocation invokeWithTarget:[[MJCat alloc] init]];    
}



#pragma mark -對于有參柏肪,有返回值的,anInvocation中也有更多的功能
//ageTest:方法是傳入int類型的age芥牌,然后乘以2再返回

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(ageTest:)) {
        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
       
        //也可以使用下面這句代替烦味,這樣就不用自己管Type Encodings了
        //return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
    int ret;
    [anInvocation getReturnValue:&ret]; //getReturnValue是獲取方法返回值,anInvocation中還有其他更多的數(shù)據(jù)
    NSLog(@"打印結(jié)果 %d",ret);
}

對于類方法來說,消息轉(zhuǎn)發(fā)時谬俄,需要有點改變:就是要將調(diào)用的方法改成+開頭柏靶,也就是類方法;另外消息接受者為類溃论,而不是實例對象屎蜓。

#pragma mark -   - (id)forwardingTargetForSelector:(SEL)aSelector對應的
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(personTest)) {
        //因為是調(diào)用類方法,所以這里要注意一下
        return [MJCat class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

當沒有實現(xiàn)forwardingTargetForSelector方法或者這個方法返回nil時钥勋,methodSignatureForSelector方法炬转,注意都是類方法,帶+號的算灸。

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(PersonClassTest)){
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    //這里調(diào)用[MJCat class]這個類
    [anInvocation invokeWithTarget:[MJCat class]];
}
5扼劈、總結(jié)
  • <1> 介紹一下OC的消息機制
    OC中的方法調(diào)用都是轉(zhuǎn)成obje_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
    objc_msgSend底層有三大階段:
    (1)消息發(fā)送(在當前類菲驴、父類中查找方法)
    (2)動態(tài)方法解析
    (3)消息轉(zhuǎn)發(fā)
    這三大階段的具體流程可以根據(jù)流程圖解釋荐吵。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赊瞬,隨后出現(xiàn)的幾起案子先煎,更是在濱河造成了極大的恐慌,老刑警劉巖巧涧,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薯蝎,死亡現(xiàn)場離奇詭異,居然都是意外死亡谤绳,警方通過查閱死者的電腦和手機良风,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闷供,“玉大人烟央,你說我怎么就攤上這事⊥嵩啵” “怎么了疑俭?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長婿失。 經(jīng)常有香客問我钞艇,道長,這世上最難降的妖魔是什么豪硅? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任哩照,我火速辦了婚禮,結(jié)果婚禮上懒浮,老公的妹妹穿的比我還像新娘飘弧。我一直安慰自己识藤,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布次伶。 她就那樣靜靜地躺著痴昧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冠王。 梳的紋絲不亂的頭發(fā)上赶撰,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音柱彻,去河邊找鬼豪娜。 笑死,一個胖子當著我的面吹牛哟楷,可吹牛的內(nèi)容都是我干的侵歇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼吓蘑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坟冲?” 一聲冷哼從身側(cè)響起磨镶,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎健提,沒想到半個月后琳猫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡私痹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年脐嫂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紊遵。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡账千,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暗膜,到底是詐尸還是另有隱情匀奏,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布学搜,位于F島的核電站娃善,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瑞佩。R本人自食惡果不足惜聚磺,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炬丸。 院中可真熱鬧瘫寝,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捣鲸,卻和暖如春瑟匆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背栽惶。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工愁溜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人外厂。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓冕象,卻偏偏與公主長得像,于是被迫代替她去往敵國和親汁蝶。 傳聞我的和親對象是個殘疾皇子渐扮,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容