Runtime - 方法發(fā)送機(jī)制土味講解

面試驅(qū)動技術(shù)合集(初中級iOS開發(fā))环鲤,關(guān)注倉庫,及時獲取更新 Interview-series

image

Class 結(jié)構(gòu)詳解

struct objc_class : objc_object {
    Class isa;
    Class superclass;
    cache_t cache;--> 方法緩存      
    class_data_bits_t bits;  
}
struct cache_t {
    struct bucket_t *_buckets;//散列表
    mask_t _mask;//散列表長度-1
    mask_t _occupied;//已經(jīng)緩存的方法數(shù)量
    }
struct bucket_t {
    cache_key_t _key;//@selecter(xxx) 作為key
    MethodCacheIMP _imp;//函數(shù)的執(zhí)行地址
    }
  • buckets 散列表古徒,是一個數(shù)組爆雹,數(shù)組里面的每一個元素就是一個bucket_t,bucket_t里面存放兩個
    • _key SEL作為key
    • _imp 函數(shù)的內(nèi)存地址
  • _mask 散列表的長度
  • _occupied已經(jīng)緩存的方法數(shù)量
image
  • 函數(shù)調(diào)用底層走的是objc_msgSend
image-20190313222359416

正常的流程:

  1. 對象通過isa,找到函數(shù)所在的類對象
  2. 這時候先做緩存查找勺三,如果緩存的函數(shù)列表中沒找到該方法
  3. 就去類的class_rw中的methods中找雷滚,如果找到了,調(diào)用并緩存該方法
  4. 如果類的class_rw中沒找到該方法吗坚,通過superclass到父類中祈远,走的邏輯還是先查緩存,緩存沒有查類里面的方法商源。
  5. 最終如果在父類中調(diào)用到了车份,會將方法緩存到當(dāng)前類的方法緩存列表中

方法緩存

如何進(jìn)行緩存查找->使用散列表(散列表 - 空間換時間)

image-20190317205913318
image-20190313220800705
MNGirl *girl = [[MNGirl alloc]init];
mj_objc_class *girlClass = (__bridge mj_objc_class *)[MNGirl class];

[girl beauty];
[girl rich];

//遍歷緩存(散列表長度 = mask + 1)
cache_t cache = girlClass->cache;
bucket_t *buckets = cache._buckets;

for (int i = 0; i < cache._mask + 1; i++) {
    
    bucket_t bucket = buckets[i];
    
    NSLog(@"%s %p", bucket,bucket._imp);
}

----------------------------------------
2019-03-13 22:11:42.911494+0800 rich 0x100000be0
2019-03-13 22:11:42.912946+0800 beauty 0x100000c10
2019-03-13 22:11:42.912970+0800 (null) 0x0
2019-03-13 22:11:42.913002+0800 init 0x7fff4f98ff4d

發(fā)現(xiàn)緩存中已經(jīng)有三個方法了,分別是初始化調(diào)用的init牡彻,第一次調(diào)用的beauty和第二次調(diào)用的rich

散列表取方法

[girl beauty];
[girl rich];

//遍歷緩存(散列表長度 = mask + 1)
cache_t cache = girlClass->cache;
bucket_t *buckets = cache._buckets;

bucket_t bucket = buckets[(long long)@selector(beauty) & cache._mask];

NSLog(@"%s %p", bucket,bucket._imp);

-----------------------------------------
2019-03-13 22:15:00 beauty 0x100000c60

確實是取方法的時候扫沼,不用遍歷出爹,通過@selector( ) & mask = index索引,數(shù)組同index就

注意缎除,不一定每次都能準(zhǔn)確的index索引严就,算出來的index取出來的內(nèi)容不一定是想要的,但是經(jīng)常是比較接近器罐,最差的情況下梢为,也只是一邊的循環(huán)遍歷

索引散列表效率遠(yuǎn)高于數(shù)組!

image-20190313223112407

方法查找的源碼: bucket_t * cache_t::find(cache_key_t k, id receiver)

bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);

bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0  ||  b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);

// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}

索引值 Index 的計算

static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
    return (mask_t)(key & mask);
}

mask_t begin = cache_hash(k, m);

走的是 key & mask的方法轰坊, A & B 一定是小于 A的

 1111 0010
&0011 1111
----------
 0011 0010 <= 原來的值

哈希表的算法也有用求余的铸董,和&類似

實現(xiàn)如下:

image-20190313223858753
(i = cache_next(i, m)) != begin

查找流程梳理: 比如起始下標(biāo)是4, 總長度是6肴沫,目標(biāo)不在列表中

  1. 取出index = 4的值袒炉,發(fā)現(xiàn)不是想要的,i - - 變成3
  2. 3 依次 - - 到0樊零,然后mask長度開始 = 6繼續(xù)
  3. 當(dāng)6 又 - - 到起始index = 4的時候我磁,說明已經(jīng)遍歷一圈了,還是沒找到驻襟,方法緩存查找結(jié)束

OC的消息機(jī)制

三個階段

  • 消息發(fā)送

  • 動態(tài)方法解析

  • 消息轉(zhuǎn)發(fā)

消息發(fā)送

當(dāng)前類查找順序

  • 排序好的列表夺艰,采用二分查找算法查找對應(yīng)的執(zhí)行函數(shù)
  • 未排序的列表,采用一般遍歷的方法查找對象執(zhí)行函數(shù)

父類逐級查找

image
image

動態(tài)方法解析

@interface IOSer : NSObject

- (void)interview;

@end

@implementation IOSer

- (void)test{
    
    NSLog(@"%s",__func__);
    
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(interview)) {
        
        Method method = class_getInstanceMethod(self, @selector(test));
        
        //動態(tài)添加interview方法
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        
        return YES;
        
    }
    return [super resolveInstanceMethod:sel];
}

@end

----------------------------------------------

//調(diào)用
IOSer *ios = [[IOSer alloc]init];
[ios interview];


---------------------------------------------
結(jié)果沉衣,不會crash郁副,進(jìn)入了動態(tài)添加的方法了
2019-03-17 21:33:51.475717+0800 Runtime-TriedResolverDemo[11419:9277997] -[IOSer test]
image-20190317214712857

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

  • 消息轉(zhuǎn)發(fā)流程1:forwardingTargetForSelector
@implementation IOSer

- (void)interview{
    
    NSLog(@"%s",__func__);
}
@end

@interface Forwarding : NSObject

- (void)interview;

@end

@implementation Forwarding

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(interview)) {
    
        //objc_msgSend([[IOSer alloc]init],aSelector)
        //由IOSer作為消息轉(zhuǎn)發(fā)的接收者
        return [[IOSer alloc]init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

---------------------------------------------------------------
調(diào)用
Forwarding *obj = [[Forwarding alloc]init];
[obj interview];


---------------------------------------------
結(jié)果,不會crash豌习,進(jìn)入了動態(tài)添加的方法了
2019-03-17 22:57:45.130805+0800 Runtime-TriedResolverDemo[13776:9355195] -[IOSer interview]
  • 消息轉(zhuǎn)發(fā)流程2:forwardingTargetForSelector
@implementation Forwarding

//返回方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(interview)) {

        //v16@0:8 = void xxx (self,_cmd)
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//NSInvocation - 方法調(diào)用
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //設(shè)置方法調(diào)用者
    [anInvocation invokeWithTarget:[[IOSer alloc]init]];
}

@end
  • NSInvocation 其實封裝了一個方法調(diào)用存谎,包括:
    • 方法名 - anInvocation.selector
    • 方法調(diào)用 - anInvocation.target
    • 方法參數(shù) - anInvocation getArgument: atIndex:
image


冷門知識補(bǔ)充

//類方法的消息轉(zhuǎn)發(fā)
[Forwarding test];

類方法也可以實現(xiàn)消息轉(zhuǎn)發(fā)肥隆,但是用的是+ (id)forwardingTargetForSelector:(SEL)aSelector函數(shù)

因為__forwarding底層既荚,是用receiver去發(fā)送 forwardingTargetForSelector消息,如果是類方法栋艳,receiver是類對象恰聘,所以要調(diào)用的是 “+” 方法

小tips:默認(rèn)是沒有+ (id)forwardingTargetForSelector:(SEL)aSelector方法,可以先打- (id)forwardingTargetForSelector:(SEL)aSelector吸占,“-” 替換成“+”晴叨,完成~



友情演出:小馬哥MJ

參考資料:

objc-msgsend

gun

libmalloc

objc4

Objective-C-Message-Sending-and-Forwarding

Type Encodings

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市矾屯,隨后出現(xiàn)的幾起案子兼蕊,更是在濱河造成了極大的恐慌,老刑警劉巖件蚕,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孙技,死亡現(xiàn)場離奇詭異产禾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)绪杏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門下愈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蕾久,你說我怎么就攤上這事势似。” “怎么了僧著?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵履因,是天一觀的道長。 經(jīng)常有香客問我盹愚,道長栅迄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任皆怕,我火速辦了婚禮毅舆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘愈腾。我一直安慰自己憋活,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布虱黄。 她就那樣靜靜地躺著悦即,像睡著了一般。 火紅的嫁衣襯著肌膚如雪橱乱。 梳的紋絲不亂的頭發(fā)上笔喉,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天项滑,我揣著相機(jī)與錄音,去河邊找鬼渐夸。 笑死锐秦,一個胖子當(dāng)著我的面吹牛悠栓,可吹牛的內(nèi)容都是我干的冷守。 我是一名探鬼主播然评,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叶摄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起安拟,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蛤吓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后糠赦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體会傲,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡锅棕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了淌山。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裸燎。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泼疑,靈堂內(nèi)的尸體忽然破棺而出德绿,到底是詐尸還是另有隱情,我是刑警寧澤退渗,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布移稳,位于F島的核電站,受9級特大地震影響会油,放射性物質(zhì)發(fā)生泄漏个粱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一翻翩、第九天 我趴在偏房一處隱蔽的房頂上張望都许。 院中可真熱鬧,春花似錦嫂冻、人聲如沸胶征。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弧烤。三九已至,卻和暖如春蹬敲,著一層夾襖步出監(jiān)牢的瞬間暇昂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工伴嗡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留急波,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓瘪校,卻偏偏與公主長得像澄暮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阱扬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345