基情四射的日子
我總是對神秘的事物產生興趣,自從知道了Objective-C的Runtime技能,我便試著接近它,討好它,因為只有對這個技能了如指掌,我才能征服它,才能在修仙的道路上順利前進。
消息傳遞函數: objc_msgSend
這個可以說是Runtime系統的根基,因為所有的OC方法調用最終都會轉換成objc_msgSend函數的調用。這么重要的秘密,沒想到只給丫喝了兩瓶啤的,它就告訴了我。看來酒量不行,難在江湖混吶。
招式破解:[receiver message] ----> objc_msgSend(receiver, selector, arg1, arg2, ...) ? ? objc_msgSend在執行的過程中,會悄悄的傳入接收對象和方法選擇器這兩個參數。
看了Runtime醉酒的樣子,我心里嘀咕著:“就這貨,怎么能有這么強大的力量?”果然不出我所料,這貨有一枚神器叫編譯器,一直在默默的幫助著它。就這個消息傳遞,也是因為編譯器為每一個類和對象構建了各自的結構,才能使其順利施放技能。那我倒是要看看,這個類結構到底是啥?
類結構破解: ? ?
類結構包含了一個指向父類的指針(isa)和一個類調度表。類調度表記錄了類中選擇器對應的具體內存地址,就像這樣:selector ----> address 。當調用一個方法,也就是給對象發送一個消息的時候,會根據isa找到類結構,并在調度表中查找匹配的selector;如果不能匹配,objc_msgSend就根據isa到父類類結構里與調度表中的selector匹配,依此類推直到NSObject類。一旦匹配到了selector,就調用表中記錄的函數(address),否則將啟動消息轉發機制。這種在運行時選擇方法實現的過程稱為動態綁定。
強大的Runtime必然會有很多的招式,這招動態綁定,我給90分。因為我覺著這樣靈活的招式,肯定會有它的弊端。首先在編譯期不能確定方法的位置,這就給運行時造成了不可避免的麻煩,從而導致效率上的問題。哼哼~果然被我找到了弱點。但是后來的一番話,差點迷惑了我。
效率提升招式之緩存:?
每個類都有單獨的緩存,包含了繼承的selector和類中定義的selector(只有在曾今調用過的方法才會進行緩存)。在匹配調度表之前,先檢查接收對象的類緩存(哈希散列算法),如果緩存中有匹配的selector,就直接使用。這大大減少了運行時查詢selector所消耗的時間。
我差點就信了。后來仔細一想,如果我連續多次調用一個特定的方法,那不就玩完了嗎?哈哈哈,丫果然有弱點,看我怎么支配你。
我也算是武學奇才,一會兒就研究了一個招式:規避動態綁定機制。
首先我要想辦法得到一個方法的內存地址,然后就直接調用啊,這不就沒有查找selector的過程了嗎。至于怎么得到方法的地址,就要靠methodForSelector方法了。請看下面的招式玩耍:
void (*showTitle)(id, SEL, NSString *);
int i;
showTitle = (void(*)(id, SEL, NSString *))[target methodForSelector:@selector(showMessage:)];
for (i=0; i<1000; ++i) {
? ? showTitle(targetList[i], @selector(showMessage:), "邪魔退散");
}
這樣在重復多次調用一個特定的方法,有顯著的效率提升。
學會了這招技能,我看著熟睡的Runtime,心想:“總有一天,在你遇到問題的時候,我會助你一臂之力的。”
不會吧,我竟然對它沒有了敵意。看來在修仙的道路上,我會多一位兄弟(jiyou)了。
關注微信公眾號CodingArtist,可以第一時間得到文章更新通知!? ^_^