please note 如文開頭所說 文章由于時(shí)間久遠(yuǎn) 可能會(huì)有代碼過時(shí)的風(fēng)險(xiǎn) 但本文只是理解原理 不用在意
譯文:
在這篇文章中瘸恼,我將審視(look at)Objective-C中的一個(gè)陌生的概念 - 元類(the meta-class)阵具。Objective-C中的每個(gè)類都有自己的關(guān)聯(lián)元類庭砍,但由于您很少直接使用元類彩届,所以它們?nèi)耘f保持神秘吨枉。我將首先看看如何在運(yùn)行時(shí)創(chuàng)建一個(gè)類仇箱。通過檢查這個(gè)創(chuàng)建的“類對(duì)”(class pair),我將解釋元類是什么东羹,并且還涵蓋了數(shù)據(jù)在Objective-C中是對(duì)象還是類的含義剂桥。
在運(yùn)行時(shí)創(chuàng)建一個(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);
添加的方法使用名為ReportFunction
其實(shí)現(xiàn)的函數(shù),其定義如下:
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è)類只需三個(gè)簡(jiǎn)單的步驟:
- 為“類對(duì)”分配存儲(chǔ)(使用
objc_allocateClassPair
)。
2.根據(jù)需要將方法和ivars添加到類中(我已經(jīng)用class_addMethod
添加了一個(gè)方法)
3.注冊(cè)該類以便可以使用(使用objc_registerClassPair
)冤议。
然而斟薇,直接的問題是:什么是“類對(duì)(class pair)”?該函數(shù)objc_allocateClassPair
只返回一個(gè)值:類恕酸。另一半在哪里堪滨?
我相信你已經(jīng)猜到了這一對(duì)的另一半是元類(meta class)(這是這篇文章的標(biāo)題),但要解釋它是什么以及為什么你需要它蕊温,我將給出一些關(guān)于對(duì)象和類的背景知識(shí)在Objective-C中袱箱。
數(shù)據(jù)結(jié)構(gòu)成為一個(gè)對(duì)象需要什么遏乔?
每個(gè)對(duì)象都有一個(gè)類。這是一個(gè)基本的面向?qū)ο蟮母拍罘⒈剩贠bjective-C中盟萨,它也是數(shù)據(jù)的基礎(chǔ)部分。任何具有指向正確位置的類的指針的數(shù)據(jù)結(jié)構(gòu)都可以視為一個(gè)對(duì)象了讨。
在Objective-C中捻激,對(duì)象的類由其isa
指針決定。該isa
指針指向?qū)ο蟮念悺?br>
實(shí)際上前计,Objective-C中對(duì)象的基本定義如下所示:
typedef struct objc_object {
Class isa;
} *id;
這就是說:任何以指向Class結(jié)構(gòu)的指針開始的結(jié)構(gòu)都可以視為一個(gè)objc_object
胞谭。
Objective-C中對(duì)象的最重要特性是可以向它們發(fā)送消息:
[@"stringValue" writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
這是有效的,因?yàn)楫?dāng)你向Objective-C對(duì)象發(fā)送消息時(shí)(比如NSCFString
這里)男杈,運(yùn)行時(shí)會(huì)跟隨對(duì)象的isa
指針來獲取對(duì)象的Class
(NSCFString
本例中的類)丈屹。該Class
則包含Methods
列表適用于所有對(duì)象 Class
和指針指向superclass
來查找繼承的方法。運(yùn)行時(shí)查看Class
和superclass
中的Methods
列表以找到與消息選擇器相匹配的一個(gè)(在上面的例子中势就,writeToFile:atomically:encoding:error
on NSString
)。運(yùn)行時(shí)然后調(diào)用該方法function(IMP
)脉漏。
重要的一點(diǎn)是Class
定義可以發(fā)送給對(duì)象(instance)的消息苞冯。
什么是元類?
現(xiàn)在侧巨,您可能已經(jīng)知道舅锄,類Class
在Objective-C中也是一個(gè)對(duì)象。這意味著你可以發(fā)送消息給一個(gè)類Class
司忱。
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
在這種情況下皇忿,defaultStringEncoding
發(fā)送給NSString
類。
這是有效的坦仍,因?yàn)槊恳粋€(gè)類Class
在Objective-C中都是一個(gè)對(duì)象本身鳍烁。這意味著Class
結(jié)構(gòu)必須以一個(gè)isa
指針開始,以便它與objc_object
上面顯示的結(jié)構(gòu)二進(jìn)制兼容繁扎,并且結(jié)構(gòu)中的下一個(gè)字段必須是指向superclass
(或nil
基類)的指針幔荒。
正如我上周展示的Class
,根據(jù)您運(yùn)行的運(yùn)行時(shí)版本梳玫,有幾種不同方式的定義 爹梁,但是可以確定的是,它們都以isa
字段開頭提澎,后跟superclass
字段姚垃。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
/* followed by runtime specific details... */
};
然而,為了讓我們?cè)陬?code>Class上調(diào)用一個(gè)方法盼忌,這個(gè)類Class
的isa
指針本身必須指向一個(gè)Class
結(jié)構(gòu)积糯,并且該Class
結(jié)構(gòu)必須包含Methods
列表掂墓,這樣我們可以在該類上調(diào)用想用的方法。
這引出了元類的定義:元類(meta-class)是Class
對(duì)象的類絮宁。
簡(jiǎn)單的說:
- 當(dāng)你向一個(gè)對(duì)象(實(shí)例)發(fā)送消息時(shí)梆暮,該消息將在對(duì)象所屬類(object's class)的方法列表中查找。
- 當(dāng)你向一個(gè)類發(fā)送一條消息時(shí)绍昂,該消息將在類的元類(class' meta-class)的方法列表中查找啦粹。
元類是必不可少的,因?yàn)樗鎯?chǔ)一個(gè)類的類方法窘游。每一個(gè)類Class
必須有一個(gè)獨(dú)一無二的元類唠椭,因?yàn)槊總€(gè)類Class
都有一個(gè)潛在的唯一的類方法列表。
元類的類是什么?
元類與Class
之前一樣忍饰,也是一個(gè)對(duì)象贪嫂。這意味著你也可以調(diào)用它的方法。當(dāng)然艾蓝,這意味著它也必須有一個(gè)類Class
力崇。
所有元類都使用基類的元類(Class
繼承層次結(jié)構(gòu)中頂層的元類)作為它們的類。這意味著對(duì)于所有從NSObject
(大多數(shù)類)中繼承下來的類赢织,元類使用NSObject
元類作為它的類亮靴。
遵循所有元類使用基類的元類作為它們的類的規(guī)則,任何基類元類都將是它自己的類(它們的isa
指針指向它們自己)于置。這意味著元類isa
上的指針指向NSObject它自己(它是它自己的一個(gè)實(shí)例)茧吊。
類和元類的繼承
以同樣的方式,Class
指向它的父類super_class
的指針八毯,元類指向元類的 super_class
利用自身的super_class
指針搓侄。
為了解決更進(jìn)一步的奇怪問題(As a further quirk 字面意思作為一個(gè)進(jìn)一步的怪癖),基類的元類將其super_class
設(shè)置為基類本身话速。
這個(gè)繼承層次的結(jié)果是層次結(jié)構(gòu)中的所有實(shí)例讶踪、類和元類都繼承了層次結(jié)構(gòu)的基類。
對(duì)于NSObject
層次結(jié)構(gòu)中的所有實(shí)例泊交,類和元類俊柔,所有NSObject
實(shí)例方法都是有效的。對(duì)于類和元類活合,所有的NSObject
類方法也是有效的雏婶。
所有這些概念在文本中有些混亂。Greg Parker匯集了一個(gè)關(guān)于實(shí)例白指,類留晚,元類和他們的超類以及它們?nèi)绾谓M合在一起的優(yōu)秀圖表。
實(shí)驗(yàn)證實(shí)
為了確認(rèn)所有這些,讓我們看看在ReportFunction
這篇文章開始時(shí)我給出的輸出結(jié)果错维。這個(gè)函數(shù)的目的是跟隨isa
指針并記錄它找到的內(nèi)容奖地。
為了運(yùn)行ReportFunction,我們需要?jiǎng)?chuàng)建一個(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];
由于沒有聲明report方法参歹,所以我使用performSelector:
來調(diào)用它,所以編譯器不會(huì)給出警告隆判。
現(xiàn)在ReportFunction
將通過isa
指針遍歷并告訴我們什么對(duì)象被用作元類犬庇、類、和元類的類侨嘀。
獲取對(duì)象的類:
遵循指針的
ReportFunction
用法object_getClass
臭挽,isa
因?yàn)?code>isa指針是類的受保護(hù)成員(不能直接訪問其他對(duì)象的isa
指針)。在ReportFunction
不使用class的方法來做到這一點(diǎn)咬腕,因?yàn)檎{(diào)用class一個(gè)方法上的Class對(duì)象不返回的元類欢峰,而是再次返回Class(所以[NSString class]
將返回NSString
類,而不是在NSString
元類)涨共。
這是NSLog程序運(yùn)行時(shí)的輸出(減前綴):
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重復(fù)追蹤該值來查看達(dá)到的地址:
- 該對(duì)象是地址0x10010c810纽帖。
- 該類是地址0x10010c600。
- 元類是地址0x10010c630举反。
- 元類的類(即NSObject元類)是地址0x7fff71038480懊直。
- 在NSObject元類的類本身。
地址的價(jià)值并不重要照筑,只是它展示了從類到meta-class到NSObject meta-class 的進(jìn)展吹截。
結(jié)論
元類是Class
對(duì)象的類瘦陈。每個(gè)Class
都有自己獨(dú)特的元類(所以每個(gè)Class
都可以擁有自己獨(dú)特的方法列表)凝危。這意味著所有的Class
對(duì)象都不是同一個(gè)類。
元類將始終確保該Class
對(duì)象具有層次結(jié)構(gòu)中基類的所有實(shí)例和類方法晨逝,以及中間的所有類方法蛾默。對(duì)于后繼類NSObject
,這意味著所有NSObject實(shí)例和協(xié)議方法都是為所有Class(和元類)對(duì)象定義的捉貌。
所有元類本身都使用基類的元類(NSObject
元類作為NSObject的繼承類)作為它們的類支鸡,包括基本級(jí)元類,它是運(yùn)行時(shí)中唯一的自定義類(self-defining class)趁窃。
PS 譯者拓展
so 看完了這篇文章 看看下圖是不是豁然明朗