objc_msgSend匯編源碼分析

引言

Objective-C是通過消息機(jī)制調(diào)用方法的痴晦,編譯器會(huì)把所有消息發(fā)送轉(zhuǎn)為objc_msgSend方法調(diào)用肛根。說到objc_msgSend的匯編實(shí)現(xiàn),大多數(shù)人會(huì)覺的是因?yàn)?strong>性能高才用匯編實(shí)現(xiàn),幾乎沒有文章說其它原因狼荞。Objective-C所有方法都會(huì)轉(zhuǎn)為objc_msgSend方法調(diào)用,然而每個(gè)方法參數(shù)和返回值都可能不一樣帮碰,參數(shù)和返回值要怎么處理相味?

  • 本文首先會(huì)結(jié)合Objective-C Runtime機(jī)制深入分析objc_msgSend匯編實(shí)現(xiàn)。
  • 本文最后會(huì)從Calling Conventions角度分析objc_msgSend實(shí)現(xiàn)殉挽,利用Calling Conventions和匯編還可以實(shí)現(xiàn)很多黑科技丰涉。

Objective-C對象結(jié)構(gòu)

Objective-C中消息發(fā)送核心數(shù)據(jù)結(jié)構(gòu)如下:

//以下代碼均為arm64平臺(tái)
typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_object {
    isa_t isa;
}

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;  //class_rw_t*
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

union isa_t 
{
    Class cls;
    uintptr_t bits;
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
}

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}

struct bucket_t {
    cache_key_t _key;//實(shí)際上是selector
    IMP _imp; //實(shí)際上是函數(shù)指針
}

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
}

NSObject子類的實(shí)例都有個(gè)isa指針,isa指向Class斯碌,Class有superclass一死、cache、實(shí)例方法傻唾、屬性投慈、protocol等Runtime信息,調(diào)用實(shí)例方法的時(shí)候就是通過isa指針找到Class冠骄,然后找到IMP調(diào)用實(shí)際的方法伪煤。
Class本身也是一個(gè)對象,也有isa指針凛辣,指向meta-class抱既,meta-class也是一個(gè)對象,有類方法等屬性扁誓,調(diào)用類方法的時(shí)候防泵,就是通過Class對象的isa指針找到meta-class,然后找到IMP調(diào)用實(shí)際的方法跋理。
實(shí)例择克、Class、meta-class關(guān)系如下圖前普,圖片來源

對象關(guān)系圖

消息機(jī)制

當(dāng)編譯器遇到一個(gè)方法調(diào)用時(shí)肚邢,它會(huì)將方法的調(diào)用翻譯成以下函數(shù)中的一個(gè),
objc_msgSend拭卿、 objc_msgSend_stret骡湖、 objc_msgSendSuper 和 objc_msgSendSuper_stret。

  • 發(fā)送給對象的父類的消息會(huì)使用 objc_msgSendSuper ;
  • 有數(shù)據(jù)結(jié)構(gòu)作為返回值的方法會(huì)使用 objc_msgSendSuper_stret 或 objc_msgSend_stret ;
  • 其它的消息都是使用 objc_msgSend 發(fā)送的峻厚。

objc_msgSend查找selector的IMP响蕴,然后調(diào)用實(shí)際的方法,主要包括以下流程:

  1. 查看cache是否有selector的IMP惠桃,如果有的話直接調(diào)用
  2. 如果沒cache浦夷,最終會(huì)調(diào)用lookUpImpOrForward辖试,從類方法列表查找IMP并緩存到cache
  3. 如果方法列表沒有則會(huì)查找基類的方法,直到最上層基類(查找基類的時(shí)候也是先查找緩存劈狐,再查找方法列表)
  4. 如果基類也沒查找到罐孝,則返回_objc_msgForward的IMP,走消息轉(zhuǎn)發(fā)流程肥缔。

我們也可以自己通過class_getMethodImplementation拿到方法IMP(IMP是實(shí)際方法的函數(shù)指針)莲兢,然后調(diào)用:

//[view addSubview:view2]
void (*funtion_pointer)(id, SEL, UIView*) = (void(*)(id, SEL, UIView*)) class_getMethodImplementation((id)view, @selector(addSubview:));
funtion_pointer(view, @selector(addSubview:), view2)

匯編源碼

objc_msgSend匯編源碼在Messengers.subproj目錄,具體匯編如下:

objc_msgSend匯編源碼
_objc_msgSend_uncached匯編源碼

objc_msgSend匯編代碼不長续膳,結(jié)合objc源碼比較容易看懂改艇。需要注意的是isa和TaggedPointer格式,isa指針不是純粹的指針坟岔,還保存很多其它信息谒兄,具體可以參考isa_t union定義,其中只有3到35位才是class指針社付,所以查找之前會(huì)通過mask轉(zhuǎn)換成class指針舵变。

isa格式

iOS系統(tǒng)為了提高性能和減小內(nèi)存,使用了TaggedPointer來表示NSNumber瘦穆、NSIndexPath等對象,對象并沒有分配內(nèi)存空間赊豌,而是把對象值保存在指針里面扛或,只有指針無法容納對象才會(huì)分配實(shí)際內(nèi)存。TaggedPointer具體格式如下圖碘饼,tag index表示具體Class熙兔,系統(tǒng)有維護(hù)一個(gè)全局映射表來保存tag index和Class的關(guān)系,具體可以查看objc_tag_index_t定義艾恼,查找到具體Class之后就跟正常oc對象一樣查找IMP了住涉。

TaggedPointer指針格式

Calling Conventions

arm64架構(gòu)是通過q0-q7和x0-x7來傳函數(shù)參數(shù),可以看到objc_msgSend沒對這幾個(gè)寄存器做任何操作钠绍,找到IMP后直接通過br x17調(diào)用IMP舆声,br告訴cpu不是子程序調(diào)用。
Objective-C所以方法發(fā)送都是通過objc_msgSend柳爽,每個(gè)方法返回值和參數(shù)都不一樣媳握,如果objc_msgSend像普通函數(shù)一樣處理參數(shù),為了處理不同參數(shù)類型和參數(shù)個(gè)數(shù)磷脯,可以使用varargs 蛾找,Objective-C調(diào)用的地方必須包裹成varargs,這樣處理非常不靈活赵誓,objc_msgSend用了個(gè)很巧妙的技巧打毛,就是對參數(shù)不做任何處理柿赊,查找到IMP后直接調(diào)用,因?yàn)樵?strong>objc_msgSend開始執(zhí)行時(shí)幻枉,棧幀(stack frame)的狀態(tài)碰声、數(shù)據(jù),和各個(gè)寄存器的組合形式展辞、數(shù)據(jù)奥邮,跟調(diào)用具體的函數(shù)指針(IMP)時(shí)所需的狀態(tài)、數(shù)據(jù)罗珍,是完全一致的洽腺,所以我們用xcode調(diào)試的時(shí)候函數(shù)棧是看不到objc_msgSend,看上去就是消息發(fā)送過程完全沒發(fā)生過覆旱,跟調(diào)用普通的c方法一摸一樣蘸朋。

黑科技

objc_msgSend用很巧妙的技巧處理參數(shù)問題,利用這種技巧可以做很多方法扣唱,比如可以實(shí)現(xiàn)Aspects的效果藕坯,在調(diào)用實(shí)際方法前做些hook操作,hook完后再調(diào)實(shí)際方法噪沙。也可以使用libffi處理參數(shù)問題炼彪,可以搞很多事情。

引用

Why objc_msgSend Must be Written in Assembly
面向切面 Aspects 源碼閱讀
What is a meta-class in Objective-C?
Objective-C 中的消息與消息轉(zhuǎn)發(fā)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末正歼,一起剝皮案震驚了整個(gè)濱河市辐马,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌局义,老刑警劉巖喜爷,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異萄唇,居然都是意外死亡檩帐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門另萤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來湃密,“玉大人,你說我怎么就攤上這事四敞」寸裕” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵目养,是天一觀的道長俩由。 經(jīng)常有香客問我,道長癌蚁,這世上最難降的妖魔是什么幻梯? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任兜畸,我火速辦了婚禮,結(jié)果婚禮上碘梢,老公的妹妹穿的比我還像新娘咬摇。我一直安慰自己,他們只是感情好煞躬,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布肛鹏。 她就那樣靜靜地躺著,像睡著了一般恩沛。 火紅的嫁衣襯著肌膚如雪在扰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天雷客,我揣著相機(jī)與錄音芒珠,去河邊找鬼。 笑死搅裙,一個(gè)胖子當(dāng)著我的面吹牛皱卓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播部逮,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼娜汁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兄朋?” 一聲冷哼從身側(cè)響起存炮,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜈漓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宫盔,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡融虽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灼芭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片有额。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖彼绷,靈堂內(nèi)的尸體忽然破棺而出巍佑,到底是詐尸還是另有隱情,我是刑警寧澤寄悯,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布萤衰,位于F島的核電站,受9級(jí)特大地震影響猜旬,放射性物質(zhì)發(fā)生泄漏脆栋。R本人自食惡果不足惜倦卖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椿争。 院中可真熱鬧怕膛,春花似錦、人聲如沸秦踪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椅邓。三九已至柠逞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間希坚,已是汗流浹背边苹。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裁僧,地道東北人个束。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像聊疲,于是被迫代替她去往敵國和親茬底。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

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