Objective-C 中的元類(meta class)

在本文咱們會看到一個在Objective-C中很陌生的概念——元類。Objective-C中的每一個類都有和本身相關聯的元類,但咱們幾乎歷來不直接使用它,它們依然是那麼神祕。咱們將開始學習怎樣在運行時建立一個類。經過建立的「class pair」,我會解釋什麼是元類,而後探討它對於Objective-C中對象和類的意義。html

在運行時建立一個類

下面的代碼在運行時建立了一個NSError的子類,而且添加了一個方法:objective-c

Objective-C數據結構

 

1函數

2學習

3atom

4spa

Class newClass =指針

    objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);code

class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");orm

objc_registerClassPair(newClass);

ReportFunction函數就是添加的實例方法,具體實現以下

Objective-C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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

}

表面上看來,這至關簡單。在運行時建立一個類只須要3個步驟:o

  1. 爲」class pair」分配內存 (使用objc_allocateClassPair).
  2. 添加方法或成員變量到有須要的類裏 (我已經使用class_addMethod添加了一個方法).
  3. 註冊類以便它能使用 (使用objc_registerClassPair).

然而,有一個很迫切的問題:什麼是「class pair」?objc_allocateClassPair函數僅返回了一個值:the class。那另外一半pair在哪?

我相信你已經猜到了,另外一半pair就是元類(這篇文章的主題)。爲了解釋它是什麼和咱們爲何須要它,還須要交代下Objective-C的對象和類的相關背景。

什麼數據結構才能稱之爲對象?

每一個對象都有類。這是面向對象的基本概念,可是在Objective-C中,它對數據結構也同樣。含有一個指針且該指針能夠正確指向類的數據結構,均可以被視做爲對象。

在Objective-C中,對象的類是isa指針決定的。isa指針指向對象所屬的類。

實際上,Objective-C中對象最基本的定義是這樣的:

Objective-C

 

1

2

3

typedef struct objc_object {

    Class isa;

} *id;

這說的是:任何帶有以指針開始並指向類結構的結構均可以被視做objc_object。

Objective-C中對象最重要的特色是你能夠發送消息給它們:

Objective-C

 

1

2

[@"stringValue"

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

這能工做是由於Objective-C對象(這兒是NSCFString)在發送消息時,運行時庫會追尋着對象的isa指針獲得了對象所屬的類(這兒是NSCFString類)。這個類包含了能應用於這個類的全部實例方法和指向超類的指針以即可以找到父類的實例方法。運行時庫檢查這個類和其超類的方法列表,找到一個匹配這條消息的方法(在上面的代碼裏,是NSString類的writeToFile:atomically:encoding:error方法)。運行時庫基於那個方法調用函數(IMP)。

重點就是類要定義這個你發送給對象的消息。

什麼是元類

如今,可能你已經知道了,Objective-C的一個類也是一個對象。這意味着你能夠發送消息給一個類。

Objective-C

 

1

NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];

在這個示例裏,defaultStringEncoding被髮送給了NSString類。

由於Objective-C中每一個類自己也是一個對象。如上面所展現的,這意味着類結構必須以一個isa指針開始,從而能夠和objc_object在二進制層面兼容,而後這個結構的下一字段必須是一個指向超類的指針(對於基類則爲nil)。

正如我上週展現的,類被定義的方式有點不一樣,依賴於你的運行時庫版本,可是,它們都以isa字段開始,隨後是superclass字段。

Objective-C

 

1

2

3

4

5

6

typedef struct objc_class *Class;

struct objc_class {

    Class isa;

    Class super_class;

    /* followed by runtime specific details... */

};

爲了調用類裏的方法,類的isa指針必須指向包含這些類方法的類結構體。

這就引出了元類的定義:元類是類對象的類。
簡單說就是:

  • 當你給對象發送消息時,消息是在尋找這個對象的類的方法列表。
  • 當你給類發消息時,消息是在尋找這個類的元類的方法列表。

元類是必不可少的,由於它存儲了類的類方法。每一個類都必須有獨一無二的元類,由於每一個類都有獨一無二的類方法。

元類的類是什麼?

元類,就像以前的類同樣,它也是一個對象。你也能夠調用它的方法。天然的,這就意味着他必須也有一個類。

全部的元類都使用根元類(繼承體系中處於頂端的類的元類)做爲他們的類。這就意味着全部NSObject的子類(大多數類)的元類都會以NSObject的元類做爲他們的類

根據這個規則,全部的元類使用根元類做爲他們的類,根元類的元類則就是它本身。也就是說基類的元類的isa指針指向他本身。

類和元類的繼承

類用 super_class指針指向了超類,一樣的,元類用super_class指向類的super_class的元類。

說的更拗口一點就是,根元類把它本身的基類設置成了super_class。

在這樣的繼承體系下,全部實例、類以及元類(meta class)都繼承自一個基類。

這意味着對於繼承於NSObject的全部實例、類和元類,他們可使用NSObject的全部實例方法,類和元類可使用NSObject的全部類方法

這些文字看起來莫名其妙難以理解。Greg Parker給出了一份精彩的圖譜來展現這些關係:

實驗證實

爲了驗證,讓咱們看看我在文章開始寫的ReportFunction 函數的輸出。這個函數的目的是跟隨isa指針並打印出它的路途。

爲了運行ReportFunction,咱們須要建立一個動態實例來建立類調用report方法。

Objective-C

 

1

2

3

4

id instanceOfNewClass =

    [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];

[instanceOfNewClass performSelector:@selector(report)];

[instanceOfNewClass release];

這裏沒有聲明report方法,但我使用performSelector:調用它,因此編譯器不會給出警告。
而後ReportFunction函數會沿着isa進行檢索,來告訴咱們class,meta-class以及meta-class的class是什麼樣的狀況:

獲得對象的類:ReportFunction 函數使用object_getClass跟蹤isa指針,由於isa指針是類的保護成員(你不能直接接收其餘對象的isa指針)。ReportFunction不使用類方法,由於在類對象裏調用類方法不能返回元類,它會再次返回這個類(所以[NSString class]會返回NSString 類而不是NSString元類)

This is the output (minus NSLog prefixes) when the program runs:
這是程序運行時的輸出(省略了NSlog前綴):

Objective-C

 

1

2

3

4

5

6

7

8

This object is 0x10010c810.

Class is RuntimeErrorSubclass, and super is NSError.

Following the isa pointer 1 times gives 0x10010c600

Following the isa pointer 2 times gives 0x10010c630

Following the isa pointer 3 times gives 0x7fff71038480

Following the isa pointer 4 times gives 0x7fff71038480

NSObject's class is 0x7fff710384a8

NSObject's meta class is 0x7fff71038480

觀察isa到達過的地址的值:

  • 對象的地址是 0x10010c810.
  • 類的地址是 0x10010c600.
  • 元類的地址是 0x10010c630.
  • 根元類(NSObject的元類)的地址是 0x7fff71038480.
  • NSObject元類的類是它自己.

這些地址的值並不重要,重要的是它們說明了文中討論的從類到meta-class到NSObject的meta-class的整個流程。

最後

元類是 Class 對象的類。每一個類(Class)都有本身獨一無二的元類(每一個類都有本身第一無二的方法列表)。這意味着全部的類對象都不一樣。

元類老是會確保類對象和基類的全部實例和類方法。對於從NSObject繼承下來的類,這意味着全部的NSObject實例和protocol方法在全部的類(和meta-class)中均可以使用。

全部的meta-class使用基類的meta-class做爲本身的基類,對於頂層基類的meta-class也是同樣,只是它指向本身而已。

相關文章
相關標籤/搜索