在這篇文章中叹螟,我關(guān)注的是 Objective-C 中的一個陌生的概念—— meta-class。在 Objective-C 中的每個類都有一個相關(guān)聯(lián)的 meta-class畏线,但是你很少會直接使用 meta-class寝殴,他們?nèi)耘f保持著神秘的面紗。我們從在運行時創(chuàng)建一個類開始市咽。通過查看 “class pair”抵蚊,我會解釋 meta-class 是什么,同時也會談?wù)勗?Objective-C 中的對象或者類相關(guān)的一些一般主題谷醉。
在運行時創(chuàng)建一個類
下面的代碼在運行時創(chuàng)建了一個 NSError 的子類同時為它添加了一個方法:
Class newClass =objc_allocateClassPair([NSError class],"RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report),(IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
添加的方法使用叫 ReportFunction 的函數(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]));
}
表面上來看遇八,非常簡單刃永。在運行時創(chuàng)建一個類只需要這三步:
1羊精、為“classpair” 創(chuàng)建存儲空間(使用objc_allocateClassPair)。2、為這個類添加所需的methods 和ivars(我已經(jīng)使用class_addMethod 添加過一個方法了)裸违。3本昏、注冊這個類涌穆,然后就可以使用了(使用objc_registerClassPair)。
然后趁舀,中級問題是:“classpair” 是什么矮烹?函數(shù)objc_allocateClassPair 只返回了一個值:這個 class。這一對中的另一個在哪奉狈?(譯注:pair 有“一對,一雙” 的意思)
我敢肯定你已經(jīng)猜到了另一半就是meta-class(就是這篇文章的標題)桑驱,但是要解釋那是什么和你為什么需要它熬的,我需要介紹一些在Objective-C 中的關(guān)于對象和類的背景知識问芬。
把一個數(shù)據(jù)結(jié)構(gòu)變?yōu)閷ο笮枰裁矗?/h4>
每個對象都有一個類此衅。這是面相對象概念的基礎(chǔ)知識,但在Objective-C 中不是這樣骑歹,它(譯注:class)同樣是這個數(shù)據(jù)的一部分道媚。每個可以被當成對象的數(shù)據(jù)結(jié)構(gòu)都在恰當?shù)奈恢糜幸粋€指向一個類的指針翘县。
在Objective-C,一個對象的類由它的isa 指針決定镀脂。isa 指針指向這個對象的Class薄翅。
事實上氓奈,在Objective-C 中的對象的定義看起來像這樣:
typedef struct objc_object {
Class isa;
} *id;
這就是說:任何結(jié)構(gòu)體只要以一個指向 Class 結(jié)構(gòu)的指針開始的就可以被當成是 objc_object舀奶。
在 Objective-C 中的對象的一個重要的特性是,你可以向它們發(fā)送消息:
[@"stringValue"
writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
你可以這么做是因為光羞,當你向一個 Objective-C 的對象(像這里的 NSCFString)發(fā)送消息的時候,runtime 沿著對象的 isa 指針找到了這個對象的 Class(這里是 NSCFString 的類)結(jié)構(gòu)體呀闻。 Class 結(jié)構(gòu)體中包含了一個這個類的方法列表和一個指向父類的指針捡多,用于查找繼承的方法铐炫。
關(guān)鍵點是 Class 結(jié)構(gòu)體中定義了你可以向一個對象發(fā)送的消息倒信。
meta-class 是什么?
現(xiàn)在榜掌,你可能已經(jīng)知道乘综,在 Objective-C 中一個 Class 也是一個對象卡辰。這就意味著你也可以向一個 Class 發(fā)送消息。
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
這里反砌,向 NSString 類發(fā)送了 defaultStringEncoding。
可以這么做是因為在 Objective-C 中每個 Class 它自己同樣也是個對象于颖。也就是說 Class 結(jié)構(gòu)體必須以 isa 指針開始嚷兔,然后就可以在二進制兼容(binary compatible)我上面介紹的 objc_object 結(jié)構(gòu)了做入,接著下一個字段必須是一個指向它的父類的指針(要是類就是基類就是 nil)竟块。
我上周已經(jīng)介紹過,定義一個類有好幾種方法蒋情,主要依賴于你正在運行的 runtime 的版本。但辕翰,是的狈谊,都是由一個 isa 字段開始然后是 superclass 字段。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
/* 以下依賴于 runtime 的具體實現(xiàn) …… */
};
然而壁榕,為了讓我們在 Class 上調(diào)用一個方法,Class 的 isa 指針必須指向一個 Class 結(jié)構(gòu)體牌里,并且那個 Class 結(jié)構(gòu)體必須包含我們可以在那個 Class 上調(diào)用的方法的列表务甥。
這就引出了 meta-class 的定義:meta-class 是 Class 對象的類(the meta-class is the class for a Class object)。
簡單來說:
當你向一個對象發(fā)送消息催享,就在那個對象的方法列表中查找那個消息哟绊。
當你想一個類發(fā)送消息,就再那個類的 meta-class 中查找那個消息攀涵。
meta-class 是必須的洽沟,因為它為一個 Class 存儲類方法。每個類都必須有一個唯一的 meta-class裆操,因為每個 Class 都有一個可能不一樣的類方法。
meta-class 的類是什么踪区?
meta-class缎岗,如之前的 Class,同樣是個對象。這就意味著你也可以在它上面調(diào)用方法鸭巴。自然的鹃祖,這就意味著它也必須有一個類(譯注:isa 指針)掌敬。
所有的 meta-class 使用它們基類的 meta-class (繼承層次中最頂層的 Class 的 meta-class)作為它們自己的類。這就是說所有繼承自 NSObject 的類(大部分的類)奔害,以 NSObject 的 meta-class 作為自己的 meta-class 的類。
遵循這個規(guī)則华临,所有的 meta-class 使用基類的 meta-class 作為他們的類,任何基類的 meta-class 將會是他們自己(它們的 isa 指向他們自己)揭厚。這就是說 NSObject 的 meta-class 的 isa 指針指向它們自己(是自己的一個實例)筛圆。
class 和 meta-class 的繼承
和 Class 以 super_class 指針指向它的父類的方法一樣椿浓,meta-class 以 super_class 指針指向 Class 的 super_class 的 meta-class。(譯注:這句話有點繞提岔,就是 super-class 一個指向 Class 的父類碱蒙,一個指向 meta-class 的父類夯巷。Class 是一般對象的類型赛惩,meta-class 是 Class 的類型喷兼。)
進一步來講,基類的 meta-class 設(shè)置 super_class 指針指向基類自己。
這個繼承層次的結(jié)果就是喷面,所有在這個繼承層次中的的實例,類和 meta-class 都繼承了基類的層次琳状。
對于所有在 NSObject 層次中的實例盒齿,類和 meta-class边翁,這就意味著所有 NSObject 的實例方法都是有效的。對于類和 meta-class叨咖,所有 NSObject 的類方法也同樣是有效的啊胶。
所有這些在字面上相當讓人困惑。Greg Parker 已經(jīng)把實例趣倾,類儒恋,meta-class 還有他們的超類以非常棒的圖解的方式聚合在一起露乏,展示他們是如何在一起工作的。
用實驗驗證這點
為了驗證這些箱锐,讓我們看看在我文章開頭提供的 ReportFunction 的輸出劳较。這個函數(shù)的目的是順著 isa 指針打引出它找到的。
要運行 ReportFunction臊恋,我們需要為這個動態(tài)創(chuàng)建的類創(chuàng)建一個實例抖仅,然后在上面調(diào)用這個方法。
id instanceOfNewClass =
[[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
因為沒有這個方法的聲明环凿,所以我使用 performSelector: 調(diào)用這個方法放吩,這樣編譯器就不會輸出警告了渡紫。
現(xiàn)在 ReportFunction 會沿著 isa 指針告訴我們這個對象使用了哪些類,meta-class 和 meta-class 的類莉测。
獲取一個對象的類:ReportFunction 使用 object_getClass 跟隨 isa 指針悔雹,因為 isa 指針是一個類中一個受保護的成員變量(你不能直接訪問其他對象的 isa 指針)欣喧。ReportFunction 沒有以類方法的形式這樣調(diào)用,因為在 Class 對象上調(diào)用類方法不會返回 meta-class益涧,而是再次返回 Class 對象(所以 [NSString class] 會返回 NSString 的類而不是 NSString 的 meta-class)闲询。
這個是程序運行后的結(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 對象的類诊赊。每個 Class 都有個不同的自己的 meta-class(因此每個 Class 都可以有一個自己不同的方法列表)。也就是說每個類的 Class 不完全相同碘箍。
meta-class 總是會保證 Class 對象會有從基類繼承的所有的的實例和類方法,加上之后繼承的類方法团搞。如從 NSObject 繼承的類多艇,就意味著在所有的 Class(和 meta-class)對象中定義了所有從 NSObject 繼承的實例和協(xié)議方法峻黍。
所有的 meta-class 使用基類的 meta-class(NSObject 的 meta-class 用于繼承自 NSObject 的類)作為他們自己的類拨匆,包括在運行時自己定義的基礎(chǔ)的 meta-class。
原文出處: cocoawithlove