在這篇文章中,我關(guān)注的是 Objective-C 中的一個(gè)陌生的概念—— meta-class。在 Objective-C 中的每個(gè)類都有一個(gè)相關(guān)聯(lián)的 meta-class,但是你很少會(huì)直接使用 meta-class,他們?nèi)耘f保持著神秘的面紗。我們從在運(yùn)行時(shí)創(chuàng)建一個(gè)類開始。通過查看 “class pair”,我會(huì)解釋 meta-class 是什么,同時(shí)也會(huì)談?wù)勗?Objective-C 中的對(duì)象或者類相關(guān)的一些一般主題。
在運(yùn)行時(shí)創(chuàng)建一個(gè)類
下面的代碼在運(yùn)行時(shí)創(chuàng)建了一個(gè) NSError 的子類同時(shí)為它添加了一個(gè)方法:
Class newClass =objc_allocateClassPair([NSError class],"RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report),(IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
添加的方法使用叫 ReportFunction 的函數(shù)作為實(shí)現(xiàn),定義如下:
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]));
}
表面上來看,非常簡單。在運(yùn)行時(shí)創(chuàng)建一個(gè)類只需要這三步:
1、為“classpair” 創(chuàng)建存儲(chǔ)空間(使用objc_allocateClassPair)。2、為這個(gè)類添加所需的methods 和ivars(我已經(jīng)使用class_addMethod 添加過一個(gè)方法了)。3、注冊(cè)這個(gè)類,然后就可以使用了(使用objc_registerClassPair)。
然后,中級(jí)問題是:“classpair” 是什么?函數(shù)objc_allocateClassPair 只返回了一個(gè)值:這個(gè) class。這一對(duì)中的另一個(gè)在哪?(譯注:pair 有“一對(duì),一雙” 的意思)
我敢肯定你已經(jīng)猜到了另一半就是meta-class(就是這篇文章的標(biāo)題),但是要解釋那是什么和你為什么需要它,我需要介紹一些在Objective-C 中的關(guān)于對(duì)象和類的背景知識(shí)。
把一個(gè)數(shù)據(jù)結(jié)構(gòu)變?yōu)閷?duì)象需要什么?
每個(gè)對(duì)象都有一個(gè)類。這是面相對(duì)象概念的基礎(chǔ)知識(shí),但在Objective-C 中不是這樣,它(譯注:class)同樣是這個(gè)數(shù)據(jù)的一部分。每個(gè)可以被當(dāng)成對(duì)象的數(shù)據(jù)結(jié)構(gòu)都在恰當(dāng)?shù)奈恢糜幸粋€(gè)指向一個(gè)類的指針。
在Objective-C,一個(gè)對(duì)象的類由它的isa 指針決定。isa 指針指向這個(gè)對(duì)象的Class。
事實(shí)上,在Objective-C 中的對(duì)象的定義看起來像這樣:
typedef struct objc_object {
Class isa;
} *id;
這就是說:任何結(jié)構(gòu)體只要以一個(gè)指向 Class 結(jié)構(gòu)的指針開始的就可以被當(dāng)成是 objc_object。
在 Objective-C 中的對(duì)象的一個(gè)重要的特性是,你可以向它們發(fā)送消息:
[@"stringValue"
writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
你可以這么做是因?yàn)?,?dāng)你向一個(gè) Objective-C 的對(duì)象(像這里的 NSCFString)發(fā)送消息的時(shí)候,runtime 沿著對(duì)象的 isa 指針找到了這個(gè)對(duì)象的 Class(這里是 NSCFString 的類)結(jié)構(gòu)體。 Class 結(jié)構(gòu)體中包含了一個(gè)這個(gè)類的方法列表和一個(gè)指向父類的指針,用于查找繼承的方法。
關(guān)鍵點(diǎn)是 Class 結(jié)構(gòu)體中定義了你可以向一個(gè)對(duì)象發(fā)送的消息。
meta-class 是什么?
現(xiàn)在,你可能已經(jīng)知道,在 Objective-C 中一個(gè) Class 也是一個(gè)對(duì)象。這就意味著你也可以向一個(gè) Class 發(fā)送消息。
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
這里,向 NSString 類發(fā)送了 defaultStringEncoding。
可以這么做是因?yàn)樵?Objective-C 中每個(gè) Class 它自己同樣也是個(gè)對(duì)象。也就是說 Class 結(jié)構(gòu)體必須以 isa 指針開始,然后就可以在二進(jìn)制兼容(binary compatible)我上面介紹的 objc_object 結(jié)構(gòu)了,接著下一個(gè)字段必須是一個(gè)指向它的父類的指針(要是類就是基類就是 nil)。
我上周已經(jīng)介紹過,定義一個(gè)類有好幾種方法,主要依賴于你正在運(yùn)行的 runtime 的版本。但,是的,都是由一個(gè) isa 字段開始然后是 superclass 字段。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
/* 以下依賴于 runtime 的具體實(shí)現(xiàn) …… */
};
然而,為了讓我們?cè)?Class 上調(diào)用一個(gè)方法,Class 的 isa 指針必須指向一個(gè) Class 結(jié)構(gòu)體,并且那個(gè) Class 結(jié)構(gòu)體必須包含我們可以在那個(gè) Class 上調(diào)用的方法的列表。
這就引出了 meta-class 的定義:meta-class 是 Class 對(duì)象的類(the meta-class is the class for a Class object)。
簡單來說:
當(dāng)你向一個(gè)對(duì)象發(fā)送消息,就在那個(gè)對(duì)象的方法列表中查找那個(gè)消息。
當(dāng)你想一個(gè)類發(fā)送消息,就再那個(gè)類的 meta-class 中查找那個(gè)消息。
meta-class 是必須的,因?yàn)樗鼮橐粋€(gè) Class 存儲(chǔ)類方法。每個(gè)類都必須有一個(gè)唯一的 meta-class,因?yàn)槊總€(gè) Class 都有一個(gè)可能不一樣的類方法。
meta-class 的類是什么?
meta-class,如之前的 Class,同樣是個(gè)對(duì)象。這就意味著你也可以在它上面調(diào)用方法。自然的,這就意味著它也必須有一個(gè)類(譯注:isa 指針)。
所有的 meta-class 使用它們基類的 meta-class (繼承層次中最頂層的 Class 的 meta-class)作為它們自己的類。這就是說所有繼承自 NSObject 的類(大部分的類),以 NSObject 的 meta-class 作為自己的 meta-class 的類。
遵循這個(gè)規(guī)則,所有的 meta-class 使用基類的 meta-class 作為他們的類,任何基類的 meta-class 將會(huì)是他們自己(它們的 isa 指向他們自己)。這就是說 NSObject 的 meta-class 的 isa 指針指向它們自己(是自己的一個(gè)實(shí)例)。
class 和 meta-class 的繼承
和 Class 以 super_class 指針指向它的父類的方法一樣,meta-class 以 super_class 指針指向 Class 的 super_class 的 meta-class。(譯注:這句話有點(diǎn)繞,就是 super-class 一個(gè)指向 Class 的父類,一個(gè)指向 meta-class 的父類。Class 是一般對(duì)象的類型,meta-class 是 Class 的類型。)
進(jìn)一步來講,基類的 meta-class 設(shè)置 super_class 指針指向基類自己。
這個(gè)繼承層次的結(jié)果就是,所有在這個(gè)繼承層次中的的實(shí)例,類和 meta-class 都繼承了基類的層次。
對(duì)于所有在 NSObject 層次中的實(shí)例,類和 meta-class,這就意味著所有 NSObject 的實(shí)例方法都是有效的。對(duì)于類和 meta-class,所有 NSObject 的類方法也同樣是有效的。
所有這些在字面上相當(dāng)讓人困惑。Greg Parker 已經(jīng)把實(shí)例,類,meta-class 還有他們的超類以非常棒的圖解的方式聚合在一起,展示他們是如何在一起工作的。
用實(shí)驗(yàn)驗(yàn)證這點(diǎn)
為了驗(yàn)證這些,讓我們看看在我文章開頭提供的 ReportFunction 的輸出。這個(gè)函數(shù)的目的是順著 isa 指針打引出它找到的。
要運(yùn)行 ReportFunction,我們需要為這個(gè)動(dòng)態(tài)創(chuàng)建的類創(chuàng)建一個(gè)實(shí)例,然后在上面調(diào)用這個(gè)方法。
id instanceOfNewClass =
[[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
因?yàn)闆]有這個(gè)方法的聲明,所以我使用 performSelector: 調(diào)用這個(gè)方法,這樣編譯器就不會(huì)輸出警告了。
現(xiàn)在 ReportFunction 會(huì)沿著 isa 指針告訴我們這個(gè)對(duì)象使用了哪些類,meta-class 和 meta-class 的類。
獲取一個(gè)對(duì)象的類:ReportFunction 使用 object_getClass 跟隨 isa 指針,因?yàn)?isa 指針是一個(gè)類中一個(gè)受保護(hù)的成員變量(你不能直接訪問其他對(duì)象的 isa 指針)。ReportFunction 沒有以類方法的形式這樣調(diào)用,因?yàn)樵?Class 對(duì)象上調(diào)用類方法不會(huì)返回 meta-class,而是再次返回 Class 對(duì)象(所以 [NSString class] 會(huì)返回 NSString 的類而不是 NSString 的 meta-class)。
這個(gè)是程序運(yùn)行后的結(jié)果(省去了 NSLog 的前綴)。
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 的地址:
- the object is address 0x10010c810.
- the class is address 0x10010c600.
- the meta-class is address 0x10010c630.
- the meta-class’s class (i.e. the NSObject meta-class) is address 0x7fff71038480.
- the NSObject meta-class’ class is itself.
地址的值不是很重要,只是演示了上面討論的從類到 meta-class 到 NSObject 的 meta-class 的過程。
結(jié)論
meta-class 是 Class 對(duì)象的類。每個(gè) Class 都有個(gè)不同的自己的 meta-class(因此每個(gè) Class 都可以有一個(gè)自己不同的方法列表)。也就是說每個(gè)類的 Class 不完全相同。
meta-class 總是會(huì)保證 Class 對(duì)象會(huì)有從基類繼承的所有的的實(shí)例和類方法,加上之后繼承的類方法。如從 NSObject 繼承的類,就意味著在所有的 Class(和 meta-class)對(duì)象中定義了所有從 NSObject 繼承的實(shí)例和協(xié)議方法。
所有的 meta-class 使用基類的 meta-class(NSObject 的 meta-class 用于繼承自 NSObject 的類)作為他們自己的類,包括在運(yùn)行時(shí)自己定義的基礎(chǔ)的 meta-class。
原文出處: cocoawithlove