http://ios.jobbole.com/81657/
在本文我們會看到一個在Objective-C中很陌生的概念——元類。Objective-C中的每個類都有和自己相關聯的元類,但我們幾乎從來不直接使用它,它們依然是那么神秘。我們將開始學習怎樣在運行時創建一個類。通過創建的“class pair”,我會解釋什么是元類,然后探討它對于Objective-C中對象和類的意義。
在運行時創建一個類
下面的代碼在運行時創建了一個NSError的子類,并且添加了一個方法:
Objective-C
1
2
3
4ClassnewClass=
objc_allocateClassPair([NSErrorclass],"RuntimeErrorSubclass",0);
class_addMethod(newClass,@selector(report),(IMP)ReportFunction,"v@:");
objc_registerClassPair(newClass);
ReportFunction函數就是添加的實例方法,具體實現如下:
Objective-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15voidReportFunction(idself,SEL_cmd)
{
NSLog(@"This object is %p.",self);
NSLog(@"Class is %@, and super is %@.",[selfclass],[selfsuperclass]);
ClasscurrentClass=[selfclass];
for(inti=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",[NSObjectclass]);
NSLog(@"NSObject's meta class is %p",object_getClass([NSObjectclass]));
}
表面上看來,這相當簡單。在運行時創建一個類只需要3個步驟:o
為”class pair”分配內存 (使用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
3typedefstructobjc_object{
Classisa;
}*id;
這說的是:任何帶有以指針開始并指向類結構的結構都可以被視作objc_object。
Objective-C中對象最重要的特點是你可以發送消息給它們:
Objective-C
1
2[@"stringValue"
writeToFile:@"/file.txt"atomically:YESencoding:NSUTF8StringEncodingerror:NULL];
這能工作是因為Objective-C對象(這兒是NSCFString)在發送消息時,運行時庫會追尋著對象的isa指針得到了對象所屬的類(這兒是NSCFString類)。這個類包含了能應用于這個類的所有實例方法和指向超類的指針以便可以找到父類的實例方法。運行時庫檢查這個類和其超類的方法列表,找到一個匹配這條消息的方法(在上面的代碼里,是NSString類的writeToFile:atomically:encoding:error方法)。運行時庫基于那個方法調用函數(IMP)。
重點就是類要定義這個你發送給對象的消息。
什么是元類
現在,可能你已經知道了,Objective-C的一個類也是一個對象。這意味著你可以發送消息給一個類。
Objective-C
1
NSStringEncodingdefaultStringEncoding=[NSStringdefaultStringEncoding];
在這個示例里,defaultStringEncoding被發送給了NSString類。
因為Objective-C中每個類本身也是一個對象。如上面所展示的,這意味著類結構必須以一個isa指針開始,從而可以和objc_object在二進制層面兼容,然后這個結構的下一字段必須是一個指向超類的指針(對于基類則為nil)。
正如我上周展示的,類被定義的方式有點不同,依賴于你的運行時庫版本,但是,它們都以isa字段開始,隨后是superclass字段。
Objective-C
1
2
3
4
5
6typedefstructobjc_class*Class;
structobjc_class{
Classisa;
Classsuper_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
4idinstanceOfNewClass=
[[newClassalloc]initWithDomain:@"someDomain"code:0userInfo:nil];
[instanceOfNewClassperformSelector:@selector(report)];
[instanceOfNewClassrelease];
這里沒有聲明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 (minusNSLogprefixes) when the program runs:
這是程序運行時的輸出(省略了NSlog前綴):
Objective-C
1
2
3
4
5
6
7
8Thisobjectis0x10010c810.
ClassisRuntimeErrorSubclass,andsuperisNSError.
Followingtheisapointer1timesgives0x10010c600
Followingtheisapointer2timesgives0x10010c630
Followingtheisapointer3timesgives0x7fff71038480
Followingtheisapointer4timesgives0x7fff71038480
NSObject's class is 0x7fff710384a8
NSObject'smetaclassis0x7fff71038480
觀察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也是一樣,只是它指向自己而已。