runtime 之 Class 和 MetaClass

首先說明,這篇文章幾乎都是抄錄的別人的博客,簡書文章,在此總結,只是為了方便記憶和以后閱讀,如果有什么失禮的地方,請大家及時指正。

大神們:(推薦閱讀原文)

  1. CSDN博客:小九
  2. CSDN博客:梵塵yst
  3. 簡書:曲年

好了,抄錄正式開始

每個Objective-C對象都有一個隱藏的數據結構,這個數據結構是Objective-C對象的第一個成員變量,它就是isa指針。
這個isa到底是什么呢?官方介紹是這樣的:

Every object is connected to the run-time system through itsisa instance variable, inherited from the NSObject class.isa identifies the object's class; it points to a structurethat's compiled from the class definition. Through isa, anobject can find whatever information it needs at run timesuch asits place in the inheritance hierarchy, the size and structure ofits instance variables, and the location of the methodimplementations it can perform in response to messages.
可見,一個對象(Object)的isa指向了這個對象的類(Class),而這個對象的類(Class)的isa指向了metaclass。這樣我們就可以找到靜態方法和變量了。
Objective-C的運行時是動態的,它能讓你在運行時為類添加方法或者去除方法以及使用反射。這在其它語言是不多見的。

類的實例對象的 isa 指向它的類;類的 isa 指向該類的 metaclass;
類的 super_class 指向其父類,如果該類為根類則值為 NULL;

metaclass 的 isa 指向根 metaclass,如果該 metaclass 是根 metaclass則指向自身;
metaclass 的 super_class 指向父 metaclass,如果該 metaclass 是根 metaclass則指向該 metaclass 對應的類;
Object-C 為每個類的定義生成兩個 objc_class ,一個普通的 class,另一個即metaclass。我們可以在運行期創建這兩個 objc_class 數據結構,然后使用 objc_addClass將 class注冊到運行時系統中,以此實現動態地創建一個新的類。
在NSObject.h里面:

@interface NSObject <NSObject> {
    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

}

這一些定義對于懂的人自然懂,不會的人根本看不懂。建議看完下面的例子,再點開參考里面的鏈接仔細看一遍。
現在我們知道的是,對于我們新建的一個類,都會有一個隱藏的屬性isa,可以通過它進行一些訪問。


我們現在新建一個類Parent,繼承于NSObject, 里面有成員方法-(void)selectorP,類方法+(void)ClassSelectorP。

再新建一個類Child,繼承于Parent,里面有成員方法-(void)selectorC, 類方法+(void)ClassSelectorC。

現在我們新建一個實例Child* child = [Chlid new];

  1. 當我們調用[child class] 的時候,child就會通過isa指針去找到Child的class。
  1. 當我們調用[child superclass]的時候,child 通過isa找到Child的class,再通過super_class,找到Parent的class。
    在這里,再普及objc_class 的兩種類型:
    class     實例對象(child、Child)的isa指向的結構體;
    metaclass  class的isa指向的一個結構體;
  2. 接著,調用[child SelectorC],child通過isa找到Child的class,在class(注意看上面 struct objc_class 的定義)的方法列表里面找到SelectorC;
  3. 再試著調用[child SelectorP],child通過isa找到Child的class,發現class里面并沒有這個方法,通過class里面的super_class找到Parent的class,在里面的方法列表找到了SelectorP;
  4. 再是類方法[Child ClassSelectorC],Child(請注意,大寫)通過isa找到Child的class,通過class的isa找到Child的metaclass,在metaclass的方法列表里面找到了ClassSelectorC;
  5. 再試著調用[Child ClassSelectorP],Child通過isa找到Child的class,通過class的isa找到Child的metaclass,發現metaclass里面并沒有這個方法,通過metaclass里面的super_class找到Parent的metaclass,在里面的方法列表找到了ClassSelectorP;
- (void)viewDidLoad {
    [super viewDidLoad];
    Class clazz = [self class];
    Class clarr = [AroundMapController class];
    Class metalclazz = objc_getMetaClass("AroundMapController");
    if (class_respondsToSelector(metalclazz, @selector(viewWillAppear:))) {
        NSLog(@"ok");
    }
    if (clarr == clazz) {
        NSLog(@"2 ok");
    }
}

這個是驗證runtime的機制,可以把這段代碼復制到自己的controller.m里面,運行一下。記得把AroundMapController改成自己的controller的名字,還有引入頭文件<objc/runtime.h>

這是幾個例子基本上已經涵蓋了大多數調用的情況。
細心的朋友可能已經發現,上面的例子中,child通過isa找到的類對象,其實就是Child 通過isa找到的class。(如果能理解這一點,基本上也算對objectC的isa機制也算入門)

最后為了理解class和metaclass的作用,大家可以換位思考一下,如果我們作為runtime的設計者,當開發者新建出來一個實例對象child的時候,我們應該存儲child的屬性和方法,同時又該如何響應其方法調用,最后還要記錄與Parent之間的繼承關系。
這時候,再來看看,這種圖。可以加深對isa機制的理解:

圖片.png

參考
http://www.cocoachina.com/iOS/20141018/9960.html
http://blog.csdn.NET/jasonblog/article/details/7246822
http://blog.csdn.Net/totogo2010/article/details/8081253

一.isa指針
要認識什么是isa指針,我們得先明確一點:

在Objective-C中,任何類的定義都是對象。類和類的實例(對象)沒有任何本質上的區別。任何對象都有isa指針。

那么什么是類呢?在xcode中用快捷鍵Shift+Cmd+O 打開文件objc.h 能看到類的定義:

圖片.png

可以看出:
Class 是一個 objc_class 結構類型的指針, id是一個 objc_object 結構類型的指針.

我們再來看看 objc_class 的定義:

圖片.png

稍微解釋一下各個參數的意思:

  1. isa:是一個Class 類型的指針. 每個實例對象有個isa的指針,他指向對象的類,而Class里也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調用時,先會從本身查找類方法的實現,如果沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個封閉的內循環。
  1. super_class:父類,如果該類已經是最頂層的根類,那么它為NULL。
  2. version:類的版本信息,默認為0
  3. info:供運行期使用的一些位標識。
  4. instance_size:該類的實例變量大小
  5. ivars:成員變量的數組

再來看看各個類實例變量的繼承關系:

  1. 每一個對象本質上都是一個類的實例。其中類定義了成員變量和成員方法的列表。對象通過對象的isa指針指向類。
  1. 每一個類本質上都是一個對象,類其實是元類(meteClass)的實例。元類定義了類方法的列表。類通過類的isa指針指向元類。
  2. 所有的元類最終繼承一個根元類,根元類isa指針指向本身,形成一個封閉的內循環。

二.runtime 機制

  1. runtime:指一個程序在運行(或者在被執行)的狀態。也就是說,當你打開一個程序使它在電腦上運行的時候,那個程序就是處于運行時刻。在一些編程語言中,把某些可以重用的程序或者實例打包或者重建成為“運行庫"。這些實例可以在它們運行的時候被連接或者被任何程序調用。
  1. objective-c中runtime:是一套比較底層的純C語言API, 屬于1個C語言庫, 包含了很多底層的C語言API。 在我們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉成了runtime的C語言代碼。

runtime的應用:

  1. 動態創建一個類(比如KVO的底層實現)
  1. 動態地為某個類添加屬性\方法, 修改屬性值\方法
  2. 遍歷一個類的所有成員變量(屬性)\所有方法
  3. 實質上,以上的是通過相關方法來獲取對象或者類的isa指針來實現的。

相關函數

  1. 增加
  1. 增加函數:class_addMethod
  2. 增加實例變量:class_addIvar
  3. 增加屬性:@dynamic標簽,或者class_addMethod,因為屬性其實就是由getter和setter函數組成
  4. 增加Protocol:class_addProtocol
  5. 獲取
  6. 獲取函數列表及每個函數的信息(函數指針、函數名等等):class_getClassMethod method_getName ...
  7. 獲取屬性列表及每個屬性的信息:class_copyPropertyList property_getName
  8. 獲取類本身的信息,如類名等:class_getName class_getInstanceSize
  9. 獲取變量列表及變量信息:class_copyIvarList
  10. 替換
  11. 將實例替換成另一個類:object_setClass
  12. 替換類方法的定義:class_replaceMethod
  13. 其他常用方法:
  14. 交換兩個方法的實現: method_exchangeImplementations.
  15. 設置一個方法的實現:method_setImplementation.

三、對meta-class的補充說明(抄錄)
比較簡單的一篇英文,重點是講解meta-class。翻譯下,加深理解。
原文標題:What is a meta-class in Objective-C?
原文地址:http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html

接下來將會探討一個在Objective-C中相對陌生的概念 -- meta-class。OC中的每一個類都會有一個與之相關聯的meta class,但是你卻幾乎永遠也不會直接使用到,它們始終籠罩著一層神秘的面紗。筆者將以運行時動態創建一個class為引,通過剖析創建的class pair來弄明白到底meta-class是什么以及更深入的了解它對于OC中對象、類的意義。

  1. 在運行時創建類
    以下代碼演示運行時創建一個NSError的子類,同時添加一個實例方法給它:
Class newClass =  
    objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);  
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");  
objc_registerClassPair(newClass);  

  1. 函數ReportFunction就是添加的實例方法的具體實現
void ReportFunction(id self, SEL _cmd)  
{  
    NSLog(@"This object is %p.",self);  
    NSLog(@"Class is %@, and super is %@.",[self class],[self superclass]);  
    Class currentClass = [self class];  
    for( int i = 1; i < 5; ++i )  
    {  
        NSLog(@"Following the isa pointer %d times gives %p",i,currentClass);  
        currentClass = object_getClass(currentClass);  
    }  
    NSLog(@"NSObject's class is %p", [NSObject class]);  
    NSLog(@"NSObject's meta class is %p",object_getClass([NSObject class]));  
}  

看起來一切都很簡單,運行時創建類只需要三步:

  1. 為"class pair"分配空間(使用objc_allocateClassPair).
  1. 為創建的類添加方法和成員(上例使用class_addMethod添加了一個方法)。
  2. 注冊你創建的這個類,使其可用(使用objc_registerClassPair)。

估計讀者馬上就要問:什么是“class pair"? objc_allocateClassPair只返回一個值:Class。那么pair的另一半在哪里呢?
是的,估計你已經猜到了這個另一半就是meta-class,也就是這篇短文的標題,但是要解釋清楚它是什么,為什么需要它,還需要交代下OC的對象與類的相關背景。

一個數據結構何以成為一個對象?

每個對象都會有一個它所屬的類。這是面向對象的基本概念,但是在OC中,這對所有數據結構有效。任何數據結構,只要在恰當的位置具有一個指針指向一個class,那么,它都可以被認為是一個對象。
在OC中,一個對象所屬于哪個類,是由它的isa指針指向的。這個isa指針指向這個對象所屬的class。
實際上,OC中對象的定義是如下的樣子:

typedef struct objc_object {  
      Class isa;  
}*id;  

這個定義表明:任何以一個指向Class的指針作為首個成員的數據結構都可以被認為是一個objc_object.
最重要的特性就是,你可以向OC中的任何對象發送消息,如下這樣:

【@”stringValue" writeToFile:@"/file.txt atomically:YES encoding: NSUTF8StringEncoding error:NULL];  

運行原理就是,當你向一個OC對象發送消息時(上文的@“stringValue”),運行時庫會根據對象的isa指針找到這個對象所屬的類(上文為例,會找到NSCFString類).這個類會包含一個所有實例方法的列表及一個指向superclass的指針以便可以找到父類的實例方法。運行時庫會在類的方法列表以及父類(們)的方法列表中尋找符合這個selector(上文為例,這個selector是"writeToFile:atomically:encoding:error")的方法。找到后即運行這個方法。關鍵點就是類要定義這個你發送給對象的消息。

什么是meta-class?

typedef struct objc_class *Class;  
struct objc_class{  
     Class isa;  
     Class super_class;  
    /*followed by runtime specific details...*/  
};  

  1. 為了可以調用類方法,這個類的isa指針必須指向一個包含這些類方法的類結構體。
    這樣就引出了meta-class的概念:meta-class是一個類對象的類。
    簡單解釋下:
  1. 當你向一個對象發送消息時,runtime會在這個對象所屬的那個類的方法列表中查找。
  2. 當你向一個類發送消息時,runtime會在這個類的meta-class的方法列表中查找。
  3. meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同。

meta-class的類又是什么呢?
完美的閉環

  1. meta-class,就像Class一樣,也是一個對象。你依舊可以向它發送消息調用函數,自然的,meta-class也會有一個isa指針指向其所屬類。所有的meta-class使用基類的meta-class作為他們的所屬類。具體而言,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己所屬的類。
  1. 根據這個規則,所有的meta-class使用基類的meta-class作為它們的類,而基類的meta-class也是屬于它自己,也就是說基類的meta-class的isa指針指向它自己。(譯:完美的閉環)

類和meta-class的繼承

  1. 就像一個類使用super_class指針指向自己的父類一樣,meta-class的super_class會指向類的super_class的meta-class。一直追溯到基類的meta-class,它的super_class會指向基類自身。(譯:萬物歸根)
  1. 這樣一來,整個繼承體系中的實例、類和meta-class都派生自繼承體系中的基類。對于NSObject繼承體系來說,NSObject的實例方法對體系中所有的實例、類和meta-class都是有效的;NSObject的類方法對于體系中所有的類和meta-class都是有效的。

實驗證明:

為了證實以上的論述,讓我們查看下開篇代碼中ReportFunction的輸出。這個函數的目的就是沿著isa指針進行打印。
為了運行ErportFunction,我們需要創建一個實例,并調用report方法。

id instanceOfNewClass = [[newClass alloc]initWithDomain:@"some Domain" code:0 userInfo:nil];  
[instanceOfNewClass performSelector:@"report)];  
[instanceOfNewClass release];  

因為我們并沒有對report方法進行聲明,所以我們使用performSelector進行調用,這樣避免編譯器警告。
然后ReportFunction函數會沿著isa進行檢索,來告訴我們class,meta-class以及meta-class的class是什么樣的情況:

【注:ReportFunction使用object_getClass來獲取isa指針指向的類,因為isa指針是一個受保護成員,你不能直接訪問其他對象的isa指針。ReportFunction沒有使用class方法是因為在一個類對象上調用這個方法是無法獲得meta-class的,它只是返回這個類而已。(所以[NSString class]只是返回NSString類,而不是NSString的meta-class]

以下是程序的輸出:

This object is 0x10010c810.  
Class is RuntimeErrorSubclass, and super is NSError.  
Followingthe isa pointer 1times gives 0x10010c600  
Followingthe isa pointer 2times gives 0x10010c630  
Followingthe isa pointer 3times gives 0x7fff71038480  
Followingthe isa pointer 4times gives 0x7fff71038480  
NSObject's class is 0x7fff710384a8  
NSObject's meta class is 0x7fff71038480  

觀察通過isa獲得的地址:
對象的地址是 0x10010c810.
類的地址是 0x10010c600.
類的meta-class地址是 0x10010c630.
類的meta-class的類地址是 0x7fff71038480.(即NSOjbect的meta-class)
NSObject的meta-class的類地址是它自身。
這些地址的值并不重要,重要的是它們說明了文中討論的從類到meta-class到NSObject的meta-class的整個流程。

結論:
meta-class是類對象的類,每個類都有自己單獨的meta-class。所有的類對象并不會屬于同一個meta-class。
meta-class要保證類對象具有繼承體系中基類的所有實例和類方法,以及繼承體系中的所有中間類方法。對于所有NSObject繼承體系下的類,NSObject的實例方法和協議方法對他們和他們meta-class的對象都要有效。
所有的meta-class使用基類的meta-class作為自己的基類,對于頂層基類的meta-class也是一樣,只是它指向自己而已。

再次說明:
這篇文章純屬抄錄,感謝各位大神的總結,如果本文有什么不對的地方,請及時指正。

作者:LiYaoPeng
鏈接:http://www.lxweimin.com/p/45fe90253519
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

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

推薦閱讀更多精彩內容

  • 首先說明,這篇文章幾乎都是抄錄的別人的博客,簡書文章,在此總結,只是為了方便記憶和以后閱讀,如果有什么失禮的地方,...
    LiYaoPeng閱讀 5,013評論 1 14
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,746評論 0 9
  • Objective-C語言是一門動態語言,他將很多靜態語言在編譯和鏈接時期做的事情放到了運行時來處理。這種動態語言...
    tigger丨閱讀 1,413評論 0 8
  • 原文出處:南峰子的技術博客 Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了...
    _燴面_閱讀 1,244評論 1 5
  • 我坐在樹蔭下看著遠處身材修長年輕的男孩路過,心里極度想要沖過去抓住他。 我想要這幅身體,用以將我這幅已然干涸枯瘦的...
    杜三quan閱讀 224評論 2 1