(譯文)什么是Objective-C中的元類(Meta-class)旭蠕?

翻譯自原文 Cocoa with love

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)單的步驟:

  1. 為“類對(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ì)象的ClassNSCFString本例中的類)丈屹。該Class則包含Methods列表適用于所有對(duì)象 Class和指針指向superclass來查找繼承的方法。運(yùn)行時(shí)查看Classsuperclass中的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è)類Classisa指針本身必須指向一個(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 看完了這篇文章 看看下圖是不是豁然明朗


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牧挣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子醒陆,更是在濱河造成了極大的恐慌瀑构,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刨摩,死亡現(xiàn)場(chǎng)離奇詭異寺晌,居然都是意外死亡世吨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門呻征,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耘婚,“玉大人,你說我怎么就攤上這事陆赋°宓唬” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵奏甫,是天一觀的道長(zhǎng)戈轿。 經(jīng)常有香客問我,道長(zhǎng)阵子,這世上最難降的妖魔是什么思杯? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮挠进,結(jié)果婚禮上色乾,老公的妹妹穿的比我還像新娘。我一直安慰自己领突,他們只是感情好暖璧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著君旦,像睡著了一般澎办。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上金砍,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天局蚀,我揣著相機(jī)與錄音,去河邊找鬼恕稠。 笑死琅绅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹅巍。 我是一名探鬼主播千扶,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼骆捧!你這毒婦竟也來了澎羞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤敛苇,失蹤者是張志新(化名)和其女友劉穎妆绞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摆碉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年塘匣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巷帝。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忌卤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出楞泼,到底是詐尸還是另有隱情驰徊,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布堕阔,位于F島的核電站棍厂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏超陆。R本人自食惡果不足惜牺弹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望时呀。 院中可真熱鬧张漂,春花似錦、人聲如沸谨娜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趴梢。三九已至漠畜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坞靶,已是汗流浹背憔狞。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滩愁,地道東北人躯喇。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓辫封,卻偏偏與公主長(zhǎng)得像硝枉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倦微,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容