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

在學習Runtime的時候,你可能要脫離原來你所認知的區域,比如:你真的了解類和對象么?你真的理解實例方法和類方法么?你真的以為你看到的就是所有的東西么?網上的那些所謂的實用技巧你真的理解什么意思么?磨刀不誤砍柴工,我們先說一下很重要的幾個概念。

1. id 以及 Class

  • (我姑且認為大家印象中:id就是對象,Class就是類。)

大家對于idClass其實并不陌生,我們做一個實驗,創建一個Person的類,然后創建一個Person對象,然后這樣:

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]));

打印結果:

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

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


從結構看功能

寫一個Class去看系統的API:
typedef struct objc_class *Class;同時你會發現有一個這個東西typedef struct objc_object *id;
發現id是一個結構體,并且里面只有一個Class類型的指針isa:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

再看Class其實也是系統定義的一個結構體,只不過結構復雜很多:

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;  //實例變量大小
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE; //成員變量鏈表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE; //方法鏈表
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;  //方法緩存(大幅提高方法調用效率)
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE; //協議鏈表
#endif

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

通過對比我們可以確認幾點:

  • 在Objective-C中,所有的類其實也是一個對象,我們可以認為id中的isa指針指向的是一個類對象,并且在Class結構體中的isa指針指向元類(后面做解釋)。
  • 在Runtime中,以obj_開頭的方法大多是針對id類型的對象進行操作,而以class_開頭方法主要是針對Class類型的類對象進行操作。
  • Runtime的以class_開頭的方法是針對與Class結構體的各個元素進行操作的。
  • 結構體元素的作用詳見注釋,不再多余贅述。

元類(Meta Class)

objc_object結構體中,有一個objc_class類型的指針,奇怪的是,在objc_class結構體中又有一個objc_class類型的指針,這也就引入了元類的概念。
首先,元類是類對象的類,同樣也就是一個對象,它的結構也是objc_class結構,有人現在會說了,你這不是扯淡嗎?這么說那元類的isa指針有指向哪里?我讀書少,你不要騙我!!!

其實這就涉及了一種特殊的機制,為了不讓這種結構無限延伸下去,Objective-C讓所有的元類的isa指針指向基類的元類,以此作為它們的所屬類。即,任何NSObject繼承體系下的元類都使用NSObject的元類作為自己的所屬類,而基類的元類的isa指針是指向它自己,并且基類的元類的父類是基類。這樣就形成了一個完美的閉環。太他媽有想法了:

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

這樣我們就可以解釋剛開始的打印結果了,第一個地址0x60000000ed30為創建的Person對象,第二,第三,第四個地址0x10702c6d0Person類對象的地址,最后一個0x10702c6a8Person類的元類的地址。(注意:元類的調用只能用object_getClass ()或者objc_getClass ()獲得,使用類對象調用class方法是無法獲取到元類的,它只是返回當前類對象而已。)

實例方法和類方法

下面我們在方法調用方面來研究一下元類存在的必然性,那就要說到實例方法和類方法發送消息的機制,在Class的結構體中有一個元素methodLists,當我們調用一個方法時,系統會在這個列表中進行查找(這里不考慮cache),同樣元類也有一個methodLists,所以:

  • 當給對象發送消息時(調用實例方法),系統會在當前對象對應的類對象的methodLists中進行查找。
  • 當給類發送消息時(調用類方法),系統會在當前類的元類的methodLists中進行查找。

也就是說類對象存儲著一個類的所有實例方法,元類存儲著一個類的所有類方法。同時每個類都會有一個單獨的元類,因為每個類的類方法基本不可能完全相同。看到這有人睡說,元類中還有很多的元素,比如成員變量的鏈表,那類變量怎么說?目前我沒有找到關于類變量的信息。

2. SEL、Method、IMP

關于SEL相信大家都很熟悉,但是對于MethodIMP就相對陌生了,下面我們詳解這幾個關于方法的數據類型。

SEL

SEL是系統在編譯過程中,會根據方法的名字以及參數序列生成一個用來區分這個方法的唯一ID編號,這個 ID 就是 SEL 類型的。我們需要注意的是,只要方法的名字和參數序列完全相同,那么它們的 ID編號就是相同的。
獲取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);

打印結果:

RuntimeSkill[1741:214328] 0x10957b985___0x10957b985___0x10957b985

Method

Method從字面上一看就是方法的意思。Method其實就是 objc_method的結構體指針,結構如下:

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

獲取Method的方法:

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

IMP

IMPImplementation,為指向函數實現的指針,如果我們能夠獲取到這個指針,則可以直接調用該方法,充分證實了它就是一個函數的指針。
獲取IMP的方法:

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

獲取到IMP之后可直接調用方法:

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結構體中,有一個ivars的鏈表結構,其中存儲著所有變量信息(Ivar的數組),每一個Ivar指針對應一個變量元素。同時通過系統的API,我們看到Ivar也是一個結構體,`typedef struct objc_ivar *Ivar,它也是一個結構題:

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
} 

對于Ivar`的操作有以下方法:

// 獲取類中指定名稱成員變量
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類變量
Ivar class_getClassVariable ( Class cls, const char *name );
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
  • 需要注意的是:class_copyIvarList這個函數函數,返回全部實例變量的數組,數組中每個Ivar指向該成員變量信息的objc_ivar結構體的指針。這個數組不包含在父類中聲明的變量。outCount指針返回數組的大小。我們必須使用free()來釋放這個數組
  • 關于類變量的傳說連聽過都沒聽過,你要是吹牛逼說你知道,那麻煩您教一下我,必有重謝,哈哈哈哈哈。

總結

對于上面的很多的示例代碼只是提供給大家幫助理解的,我提醒你一點:開發中千萬不要這么寫代碼,不然你升職加薪,迎娶白富美,走上人生巔峰本來就不可能,現在可能連溫飽也是個問題了。這也不是單純的扯淡,明確概念之后,我們講Runtime的實際用法才事半功倍。

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

再提示一遍,開發中千萬不要裝逼這樣寫!!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容