討論Objective-C的一個(gè)奇怪的概念 meta-class
在Objective-C中的每個(gè)類杖狼,都有它自己相關(guān)的meta-class掌腰,但因?yàn)槟愫苌僦苯邮褂胢eta-class,所以顯得很神秘冒窍。
在運(yùn)行時(shí)建立一個(gè)類
下面的代碼在運(yùn)行時(shí)創(chuàng)建一個(gè)NSError新的子類俯树,并添加一個(gè)方法到里面:
Class newClass =
objc_allocateClassPair([NSError class], “RuntimeErrorSubclass”, 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, “v@:”);
objc_registerClassPair(newClass);
添加的這個(gè)方法用ReportFunction函數(shù)名作為它的實(shí)現(xiàn)闰歪,實(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]));
}
表面上榛泛,這都很簡(jiǎn)單蝌蹂。在運(yùn)行時(shí)創(chuàng)建一個(gè)新類,只需要3步
1)為 class pair分配存儲(chǔ)空間 (使用objc_allocateClassPair)
2)增加需要的方法和ivars(使用class_addMethod來添加方法)
3) 注冊(cè)這個(gè)類曹锨,以便它能被別人使用(objc_registerClassPair)
現(xiàn)在的問題是孤个,什么是class pair, 函數(shù)objc_allocateClassPair只返回一個(gè)值:the class
那么pair的另外一半在哪里呢?你可能已經(jīng)猜到另外一般就是meta-class(也就是本文的主題)
一個(gè)數(shù)據(jù)結(jié)構(gòu)需要哪些東西才能成為一個(gè)對(duì)象
每個(gè)對(duì)象都有一個(gè)類沛简,這是一個(gè)基本的面向?qū)ο蟮母拍睢?/p>
在Objective-C中齐鲤,任何數(shù)據(jù)結(jié)構(gòu),如果在正確的位置有一個(gè)指向類的指針椒楣,就能被視為一個(gè)對(duì)象给郊。
在Objective-C中,一個(gè)對(duì)象的類捧灰,由它的isa指針決定淆九。這個(gè)isa指針指向 對(duì)象的類。
事實(shí)上凤壁,一個(gè)對(duì)象的基本定義是這樣的:
typedef struct objc_object {
Class isa;
} *id;
這就是說吩屹,任何以一個(gè)指向Class結(jié)構(gòu)的指針開始的結(jié)構(gòu),都能被當(dāng)作objc_object
對(duì)象最重要的特性拧抖,就是你可以給它們發(fā)送消息:
[@"stringValue"
writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
當(dāng)你發(fā)送消息給一個(gè)Objective-C對(duì)象時(shí)(比如這里的NSCFString), 運(yùn)行時(shí)(runtime) 通過對(duì)象的isa指針得到對(duì)象的Class(這里是NSCFString類)煤搜,而Class里含有那些可以應(yīng)用這個(gè)類的所有對(duì)象上的所有方法的列表,以及指向superclass的指針唧席。運(yùn)行時(shí)通過類的方法列表和超類擦盾,來發(fā)現(xiàn)一個(gè)能同消息選擇子匹配的方法(上面的例子中,就是在NSString類中的writeToFile:atomically:encoding:error方法)淌哟。
要點(diǎn)就是:類定義了那些消息迹卢,你只能發(fā)送那些已經(jīng)定義好的消息給它的對(duì)象
什么是meta-class
現(xiàn)在,你可能已經(jīng)知道徒仓,在Objective-C中腐碱,一個(gè)類也是一個(gè)對(duì)象。這意味著,你也可以發(fā)送消息給一個(gè)類
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
在這種情況下症见, defaultStringEncoding被發(fā)送給NSString類
在Objective-C 中喂走,每個(gè)類,都是一個(gè)對(duì)象谋作。也就是說芋肠,類結(jié)構(gòu)也必須以isa指針開始,這樣遵蚜,它才同objc_object結(jié)構(gòu)二進(jìn)制兼容
在結(jié)構(gòu)里的第2個(gè)項(xiàng)目帖池,必須是superclass的指針(如果是基類,沒有父類的話吭净,設(shè)置為nil)
定義一個(gè)類睡汹,有很多不同的方式,依賴于你的運(yùn)行時(shí)版本而不同攒钳,但他們都以 isa開始帮孔,然后后面接著superclass
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
/* followed by runtime specific details… */
};
為了讓我們調(diào)用類的一個(gè)方法雷滋,類的isa指針必須指向一個(gè)類結(jié)構(gòu)不撑,并且,類結(jié)構(gòu)必須含有我們能在該類上調(diào)用的方法列表
這就導(dǎo)致了一個(gè)meta-class的定義:meta-class是一個(gè)類對(duì)象的類
簡(jiǎn)單地說晤斩,
當(dāng)你發(fā)送一條消息給一個(gè)對(duì)象時(shí)焕檬,這條消息會(huì)在對(duì)象的類的方法列表里查找
當(dāng)你發(fā)送一條消息給一個(gè)類時(shí),就會(huì)在類的meta-class的方法列表理查找消息
meta-class是必不可少的澳泵,因?yàn)樗鎯?chǔ)了一個(gè)類的類 方法实愚。每個(gè)類都必須只有唯一的meta-class,因?yàn)槊總€(gè)類都只可能有一個(gè)唯一的類方法列表。
meta-class的類又是什么呢兔辅?
meta-class腊敲,跟 類一樣,它也是一個(gè)對(duì)象维苔。這意味著碰辅,你也可以在它上面調(diào)用方法。自然地介时,這意味這没宾,它也必須有一個(gè)類。
所有的meta-class都使用基類的meta-class(在它們的繼承體系中沸柔,最頂層的類的meta-class)作為它們自己的類循衰。這意味著,所有從NSObject繼承來的類褐澎,它們的meta-class都將NSObject的meta-class作為自己的類
遵循這個(gè)規(guī)則会钝,所有的meta-class使用基類的meta-class作為它們自己的類,任何base meta-class都將是它自己的類(它們的isa指針指向它們自己)工三。也就是說迁酸,在 NSObject的meta-class的isa指針將指向它自己(它是自己的一個(gè)實(shí)例)
類和 meta-class的繼承
同樣的方式咽弦,類用super_class 指針指向超類,meta-class使用它自己的super_class指向 類的super-class的meta-class
巧合地是胁出,基類的meta-class設(shè)置它的 super_class 為基類自己型型。
用實(shí)驗(yàn)來驗(yàn)證我們的想法
為了確認(rèn)這些情況,我們看看ReportFunctional的輸出全蝶。 這個(gè)函數(shù)的目的是 追蹤isa指針闹蒜,并記錄在哪里找到的它。
為了運(yùn)行ReportFunction抑淫,我們需要建立這個(gè)動(dòng)態(tài)創(chuàng)建的類的實(shí)例绷落,然后調(diào)用它的report方法
id instanceOfNewClass =
[[newClass alloc] initWithDomain:@”someDomain” code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
因?yàn)闆]有report方法的聲明,我使用performSelector:來調(diào)用它始苇,所以編譯不會(huì)給出什么警告
ReportFunction將遍歷isa指針砌烁,告訴我們那些對(duì)象被當(dāng)成類,meta-class催式,以及meta-class的類 來使用
取得一個(gè)對(duì)象的類:ReportFunction將使用object_getClass來追蹤isa指針函喉, 因?yàn)閕sa指針是類的一個(gè)被保護(hù)的成員(你不能直接訪問其他類的isa指針)
ReportFunction不使用類方法來實(shí)現(xiàn)這個(gè),因?yàn)檎{(diào)用一個(gè)類對(duì)象的類方法荣月,將不會(huì)返回meta-class. 而是再次返回這個(gè)類(所以[NSString class]將返回NSString類管呵,而不是NSString的meta-class)
結(jié)論:
meta-class是類對(duì)象的類。每個(gè)類都有它自己唯一的meta-class(因?yàn)槊總€(gè)類都有它自己唯一的方法列表)