在本文咱們會看到一個在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
objc_allocateClassPair
).class_addMethod添加了一個方法
).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
.0x7fff71038480
.這些地址的值並不重要,重要的是它們說明了文中討論的從類到meta-class到NSObject的meta-class的整個流程。
元類是 Class 對象的類。每一個類(Class)都有本身獨一無二的元類(每一個類都有本身第一無二的方法列表)。這意味着全部的類對象都不一樣。
元類老是會確保類對象和基類的全部實例和類方法。對於從NSObject繼承下來的類,這意味着全部的NSObject實例和
protocol方法在全部的類(和meta-class)中均可以使用。
全部的meta-class使用基類的meta-class做爲本身的基類,對於頂層基類的meta-class也是同樣,只是它指向本身而已。