Runtime奇技淫巧之類(Class)和對(duì)象(id)以及方法(SEL)

在學(xué)習(xí)Runtime的時(shí)候翎迁,你可能要脫離原來你所認(rèn)知的區(qū)域,比如:你真的了解類和對(duì)象么净薛?你真的理解實(shí)例方法和類方法么汪榔?你真的以為你看到的就是所有的東西么?網(wǎng)上的那些所謂的實(shí)用技巧你真的理解什么意思么肃拜?磨刀不誤砍柴工痴腌,我們先說一下很重要的幾個(gè)概念。

1. id 以及 Class

  • (我姑且認(rèn)為大家印象中:id就是對(duì)象燃领,Class就是類士聪。)

大家對(duì)于idClass其實(shí)并不陌生,我們做一個(gè)實(shí)驗(yàn)猛蔽,創(chuàng)建一個(gè)Person的類剥悟,然后創(chuàng)建一個(gè)Person對(duì)象,然后這樣:

Person* person = [[Person alloc] init];
NSLog(@"%p",person);
//
NSLog(@"%p",[person class]);
NSLog(@"%p",[Person class]);
//
NSLog(@"%p",object_getClass(person));
NSLog(@"%p",object_getClass([person class]));

打印結(jié)果:

RuntimeSkill[2048:247155] 0x60000000ed30
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6d0
RuntimeSkill[2048:247155] 0x10702c6a8

我擦嘞,就問你懵逼了沒有懦胞?


從結(jié)構(gòu)看功能

寫一個(gè)Class去看系統(tǒng)的API:
typedef struct objc_class *Class;同時(shí)你會(huì)發(fā)現(xiàn)有一個(gè)這個(gè)東西typedef struct objc_object *id;
發(fā)現(xiàn)id是一個(gè)結(jié)構(gòu)體替久,并且里面只有一個(gè)Class類型的指針isa:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

再看Class其實(shí)也是系統(tǒng)定義的一個(gè)結(jié)構(gòu)體,只不過結(jié)構(gòu)復(fù)雜很多:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;  //父類
    const char *name                                         OBJC2_UNAVAILABLE; //類名
    long version                                             OBJC2_UNAVAILABLE; //版本信息
    long info                                                OBJC2_UNAVAILABLE; //類信息
    long instance_size                                       OBJC2_UNAVAILABLE;  //實(shí)例變量大小
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE; //成員變量鏈表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE; //方法鏈表
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;  //方法緩存(大幅提高方法調(diào)用效率)
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE; //協(xié)議鏈表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

通過對(duì)比我們可以確認(rèn)幾點(diǎn):

  • 在Objective-C中躏尉,所有的類其實(shí)也是一個(gè)對(duì)象蚯根,我們可以認(rèn)為id中的isa指針指向的是一個(gè)類對(duì)象,并且在Class結(jié)構(gòu)體中的isa指針指向元類(后面做解釋)胀糜。
  • 在Runtime中颅拦,以obj_開頭的方法大多是針對(duì)id類型的對(duì)象進(jìn)行操作,而以class_開頭方法主要是針對(duì)Class類型的類對(duì)象進(jìn)行操作教藻。
  • Runtime的以class_開頭的方法是針對(duì)與Class結(jié)構(gòu)體的各個(gè)元素進(jìn)行操作的距帅。
  • 結(jié)構(gòu)體元素的作用詳見注釋,不再多余贅述括堤。

元類(Meta Class)

objc_object結(jié)構(gòu)體中碌秸,有一個(gè)objc_class類型的指針,奇怪的是悄窃,在objc_class結(jié)構(gòu)體中又有一個(gè)objc_class類型的指針讥电,這也就引入了元類的概念。
首先轧抗,元類是類對(duì)象的類恩敌,同樣也就是一個(gè)對(duì)象,它的結(jié)構(gòu)也是objc_class結(jié)構(gòu)横媚,有人現(xiàn)在會(huì)說了纠炮,你這不是扯淡嗎?這么說那元類的isa指針有指向哪里灯蝴?我讀書少恢口,你不要騙我!G钤辍弧蝇!

其實(shí)這就涉及了一種特殊的機(jī)制,為了不讓這種結(jié)構(gòu)無限延伸下去折砸,Objective-C讓所有的元類的isa指針指向基類的元類,以此作為它們的所屬類沙峻。即睦授,任何NSObject繼承體系下的元類都使用NSObject的元類作為自己的所屬類,而基類的元類的isa指針是指向它自己摔寨,并且基類的元類的父類是基類去枷。這樣就形成了一個(gè)完美的閉環(huán)。太他媽有想法了:

圖畫的不好,大家將就著看

這樣我們就可以解釋剛開始的打印結(jié)果了删顶,第一個(gè)地址0x60000000ed30為創(chuàng)建的Person對(duì)象竖螃,第二,第三逗余,第四個(gè)地址0x10702c6d0Person類對(duì)象的地址特咆,最后一個(gè)0x10702c6a8Person類的元類的地址。(注意:元類的調(diào)用只能用object_getClass ()或者objc_getClass ()獲得录粱,使用類對(duì)象調(diào)用class方法是無法獲取到元類的腻格,它只是返回當(dāng)前類對(duì)象而已。)

實(shí)例方法和類方法

下面我們?cè)诜椒ㄕ{(diào)用方面來研究一下元類存在的必然性啥繁,那就要說到實(shí)例方法和類方法發(fā)送消息的機(jī)制菜职,在Class的結(jié)構(gòu)體中有一個(gè)元素methodLists,當(dāng)我們調(diào)用一個(gè)方法時(shí)旗闽,系統(tǒng)會(huì)在這個(gè)列表中進(jìn)行查找(這里不考慮cache)酬核,同樣元類也有一個(gè)methodLists,所以:

  • 當(dāng)給對(duì)象發(fā)送消息時(shí)(調(diào)用實(shí)例方法)适室,系統(tǒng)會(huì)在當(dāng)前對(duì)象對(duì)應(yīng)的類對(duì)象的methodLists中進(jìn)行查找嫡意。
  • 當(dāng)給類發(fā)送消息時(shí)(調(diào)用類方法),系統(tǒng)會(huì)在當(dāng)前類的元類的methodLists中進(jìn)行查找亭病。

也就是說類對(duì)象存儲(chǔ)著一個(gè)類的所有實(shí)例方法鹅很,元類存儲(chǔ)著一個(gè)類的所有類方法。同時(shí)每個(gè)類都會(huì)有一個(gè)單獨(dú)的元類罪帖,因?yàn)槊總€(gè)類的類方法基本不可能完全相同促煮。看到這有人睡說整袁,元類中還有很多的元素菠齿,比如成員變量的鏈表,那類變量怎么說坐昙?目前我沒有找到關(guān)于類變量的信息绳匀。

2. SEL、Method炸客、IMP

關(guān)于SEL相信大家都很熟悉疾棵,但是對(duì)于MethodIMP就相對(duì)陌生了,下面我們?cè)斀膺@幾個(gè)關(guān)于方法的數(shù)據(jù)類型痹仙。

SEL

SEL是系統(tǒng)在編譯過程中是尔,會(huì)根據(jù)方法的名字以及參數(shù)序列生成一個(gè)用來區(qū)分這個(gè)方法的唯一ID編號(hào),這個(gè) ID 就是 SEL 類型的开仰。我們需要注意的是拟枚,只要方法的名字和參數(shù)序列完全相同,那么它們的 ID編號(hào)就是相同的薪铜。
獲取SEL的幾種方法:

SEL aSel = @selector(didReceiveMemoryWarning);
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
SEL a_Sel = sel_registerName("didReceiveMemoryWarning");
NSLog(@"%p___%p___%p",aSel,a_sel,a_Sel);

打印結(jié)果:

RuntimeSkill[1741:214328] 0x10957b985___0x10957b985___0x10957b985

Method

Method從字面上一看就是方法的意思。Method其實(shí)就是 objc_method的結(jié)構(gòu)體指針恩溅,結(jié)構(gòu)如下:

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;  //方法名
    char *method_types                                       OBJC2_UNAVAILABLE;  //參數(shù)類型以及返回值類型編碼
    IMP method_imp                                           OBJC2_UNAVAILABLE; //方法實(shí)現(xiàn)指針
}  

獲取Method的方法:

// 獲取實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

IMP

IMPImplementation隔箍,為指向函數(shù)實(shí)現(xiàn)的指針,如果我們能夠獲取到這個(gè)指針脚乡,則可以直接調(diào)用該方法蜒滩,充分證實(shí)了它就是一個(gè)函數(shù)的指針。
獲取IMP的方法:

//通過Method獲取IMP
IMP method_getImplementation(Method m);
// 返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

獲取到IMP之后可直接調(diào)用方法:

SEL aSel = @selector(didReceiveMemoryWarning);
Method method = class_getInstanceMethod([self class], aSel);
IMP imp = method_getImplementation(method);
((void (*) (id, SEL)) (void *)imp)(self, aSel);

3. Ivar

Class結(jié)構(gòu)體中每窖,有一個(gè)ivars的鏈表結(jié)構(gòu)帮掉,其中存儲(chǔ)著所有變量信息(Ivar的數(shù)組),每一個(gè)Ivar指針對(duì)應(yīng)一個(gè)變量元素窒典。同時(shí)通過系統(tǒng)的API蟆炊,我們看到Ivar也是一個(gè)結(jié)構(gòu)體,`typedef struct objc_ivar *Ivar瀑志,它也是一個(gè)結(jié)構(gòu)題:

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
} 

對(duì)于Ivar`的操作有以下方法:

// 獲取類中指定名稱成員變量
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類變量
Ivar class_getClassVariable ( Class cls, const char *name );
// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
  • 需要注意的是:class_copyIvarList這個(gè)函數(shù)函數(shù)涩搓,返回全部實(shí)例變量的數(shù)組,數(shù)組中每個(gè)Ivar指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針劈猪。這個(gè)數(shù)組不包含在父類中聲明的變量昧甘。outCount指針返回?cái)?shù)組的大小。我們必須使用free()來釋放這個(gè)數(shù)組战得。
  • 關(guān)于類變量的傳說連聽過都沒聽過充边,你要是吹牛逼說你知道,那麻煩您教一下我常侦,必有重謝浇冰,哈哈哈哈哈。

總結(jié)

對(duì)于上面的很多的示例代碼只是提供給大家?guī)椭斫獾牧觯姨嵝涯阋稽c(diǎn):開發(fā)中千萬不要這么寫代碼肘习,不然你升職加薪,迎娶白富美坡倔,走上人生巔峰本來就不可能漂佩,現(xiàn)在可能連溫飽也是個(gè)問題了。這也不是單純的扯淡罪塔,明確概念之后投蝉,我們講Runtime的實(shí)際用法才事半功倍。

傳送門 : Runtime實(shí)用技巧(不扯淡征堪,不套路)

再提示一遍墓拜,開發(fā)中千萬不要裝逼這樣寫!G肫酢咳榜!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市爽锥,隨后出現(xiàn)的幾起案子涌韩,更是在濱河造成了極大的恐慌,老刑警劉巖氯夷,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臣樱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡腮考,警方通過查閱死者的電腦和手機(jī)雇毫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踩蔚,“玉大人棚放,你說我怎么就攤上這事∠诿觯” “怎么了飘蚯?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)福也。 經(jīng)常有香客問我局骤,道長(zhǎng),這世上最難降的妖魔是什么暴凑? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任峦甩,我火速辦了婚禮,結(jié)果婚禮上现喳,老公的妹妹穿的比我還像新娘凯傲。我一直安慰自己,他們只是感情好拿穴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布泣洞。 她就那樣靜靜地躺著,像睡著了一般默色。 火紅的嫁衣襯著肌膚如雪球凰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天腿宰,我揣著相機(jī)與錄音呕诉,去河邊找鬼。 笑死吃度,一個(gè)胖子當(dāng)著我的面吹牛甩挫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椿每,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼伊者,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼英遭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起亦渗,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤挖诸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后法精,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體多律,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年搂蜓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狼荞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帮碰,死狀恐怖相味,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情收毫,我是刑警寧澤攻走,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站此再,受9級(jí)特大地震影響昔搂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜输拇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一摘符、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧策吠,春花似錦逛裤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蟀给,卻和暖如春蝙砌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跋理。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工择克, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人前普。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓肚邢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拭卿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骡湖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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