Why is MetaClass in Objective-C删咱?

這篇文章源于美團(tuán)面試官問(wèn)的我一個(gè)問(wèn)題蚁孔,為什么Objective-C中有Class和MetaClass這種設(shè)計(jì)奶赔?去掉是否可以?當(dāng)時(shí)的我并沒(méi)有深入思考過(guò)這個(gè)問(wèn)題杠氢,而網(wǎng)上搜索的結(jié)果都是在闡述有MetaClass而簡(jiǎn)略的解釋了原因站刑。我認(rèn)為這個(gè)問(wèn)題是個(gè)很關(guān)鍵的問(wèn)題,花了大概兩周時(shí)間查閱資料鼻百,查看源碼绞旅。這篇文章試圖展開(kāi)探討一個(gè)問(wèn)題,為什么Objective-C中有MetaClass這個(gè)設(shè)計(jì)愕宋?

前置知識(shí)

首先簡(jiǎn)單分析下在Objective-C中玻靡,對(duì)象是什么结榄。下面源碼基于Runtime-709分析中贝。

typedef struct objc_object *id;//id其實(shí)是一個(gè)object結(jié)構(gòu)體的指針,所以id不用加*
typedef struct objc_class *Class;//Class是class結(jié)構(gòu)體的指針

struct objc_object {
    Class isa;
};

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;     // 用來(lái)緩存指針和虛函數(shù)表  
    class_data_bits_t bits;  //方法列表等
    //...
}

可以看到臼朗,對(duì)象最基本的就是有一個(gè)isa指針邻寿,指向他的class,而Class本身是繼承自object视哑。isa指針的理解誒就是英文is a绣否,代表“xxx is a (class)”。那么也就是說(shuō)挡毅,一個(gè)對(duì)象的isa指向哪個(gè)class蒜撮,代表它是那個(gè)類的對(duì)象。那么對(duì)于class來(lái)說(shuō)跪呈,它也是一個(gè)對(duì)象段磨,它的isa指針指向什么呢?

對(duì)于Class來(lái)說(shuō)耗绿,也就需要一個(gè)描述他的類苹支,也就是“類的類”,而meta正是“關(guān)于某事自身的某事”的解釋误阻,所以MetaClass就因此而生了债蜜。

而從runtime動(dòng)態(tài)生成一個(gè)類的Api的方法中晴埂,我們也可以發(fā)現(xiàn)metaClass的蹤跡。

Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes)
{
    Class cls, meta;

    rwlock_writer_t lock(runtimeLock);

    // 如果 Class 名字已存在或父類沒(méi)有通過(guò)認(rèn)證則創(chuàng)建失敗
    if (getClass(name)  ||  !verifySuperclass(superclass, true/*rootOK*/)) {
        return nil;
    }

    //分配空間
    cls  = alloc_class_for_subclass(superclass, extraBytes);
    meta = alloc_class_for_subclass(superclass, extraBytes);

    //構(gòu)建meta和class的關(guān)系
    objc_initializeClassPair_internal(superclass, name, cls, meta);

    return cls;
}

通過(guò)這個(gè)方法生成后寻定,就成了大家熟悉的那張圖儒洛。

v2-ce4b3fa6d104a632f4f34dec0d50f71f_r

從這張圖上,我們可以看到通過(guò)這么一層繼承關(guān)系狼速,Objective-C的對(duì)象原型繼承鏈就完整了晶丘。

同時(shí),實(shí)例的實(shí)例方法函數(shù)存在類結(jié)構(gòu)體中唐含,類方法函數(shù)存在metaclass結(jié)構(gòu)體中浅浮,而Objective-C的方法調(diào)用(消息)就會(huì)根據(jù)對(duì)象去找isa指針指向的Class對(duì)象中的方法列表找到對(duì)應(yīng)的方法。

Python中的MetaClass

再講Objective-C之前捷枯,先講講別的語(yǔ)言的設(shè)計(jì)滚秩,通過(guò)各種語(yǔ)言的比較,可以從更廣的層面去理解語(yǔ)言的設(shè)計(jì)思想淮捆。而之所以先講起Python郁油,是因?yàn)槲以谒阉鱉etaClass時(shí),搜索結(jié)果中大部分其實(shí)是講Python中MetaClass的攀痊。

先看看Python中一個(gè)對(duì)象結(jié)構(gòu)是怎么樣的桐腌,以下源碼基于CPython 3.7.0 alpha 1

//object.h
typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;//引用計(jì)數(shù)
    struct _typeobject *ob_type;//類型
} PyObject;

和Objective-C中類似苟径,ob_type其實(shí)就是一個(gè)isa指針案站,代表是什么類型。

而再看看PyTypeObject是怎么樣的棘街。

//object.h
typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
   //....
} PyTypeObject;

#define PyObject_VAR_HEAD      PyVarObject ob_base;

typedef struct {
    PyObject ob_base;  
    Py_ssize_t ob_size; //對(duì)象長(zhǎng)度
} PyVarObject;

PyVarObject是一種可變長(zhǎng)度對(duì)象蟆盐,是在PyObject基礎(chǔ)上加上了對(duì)象的長(zhǎng)度。而開(kāi)始的內(nèi)存包括了ob_base這個(gè)PyObject遭殉,就代表可以用PyObject指針進(jìn)行引用石挂。所以可以說(shuō),結(jié)構(gòu)體中剛開(kāi)始的部分是一個(gè)PyObject對(duì)象险污,在Python中引用就是一個(gè)對(duì)象痹愚。那么PyTypeObject開(kāi)頭是一個(gè)PyVarObject,也就是一個(gè)對(duì)象蛔糯。也就是說(shuō)拯腮,Python里的Class,也是一個(gè)對(duì)象渤闷。

#在python中生成一個(gè)Class
MyClass = type('MyClass', (), {})           

先看看Python里面的type關(guān)鍵字是什么疾瓮。

//bltinmodule.c
SETBUILTIN("type",                  &PyType_Type);
//typeobject.c
PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    //.....
    type_init,                                  /* tp_init */
    //....                                
    type_new,                                   /* tp_new */
    //....
};

可以發(fā)現(xiàn)type關(guān)鍵字是PyType_Type的一個(gè)引用,而PyType_Type是返回一個(gè)PyTypeObject飒箭,生成類的對(duì)象狼电。而PyVarObject_HEAD_INIT遞歸引用了自己(PyType_Type)作為它的type蜒灰,所以可以得知type(class) == type 。也就是說(shuō)肩碟,Python中類的isa指針指向type强窖,也就說(shuō)type其實(shí)就是MetaClass,而同時(shí)type(type) == type削祈,也就是type的isa指針指向type自身翅溺。那么Python的對(duì)象鏈就如下圖。

86fbc69def5f2afddd652a5d83e69456_b

而Objective-C不太一樣的是髓抑,并不是每一個(gè)類都有一個(gè)MetaClass咙崎,而是所有的類默認(rèn)都是同一個(gè)MetaClass。當(dāng)然吨拍,Python里可以自定義新的MetaClass褪猛。

Python中為何要使用元類的原因可能是,Python希望讓使用者對(duì)類有著最高的控制權(quán)羹饰,可以通過(guò)對(duì)元類的自定義而改變制造類的過(guò)程(例如Django里的ORM)伊滋。也就是,Python開(kāi)放了面向?qū)ο笾?strong>類的制造者的權(quán)限队秩。而同時(shí)笑旺,根據(jù)StackOverFlow這個(gè)問(wèn)答,Python的類的設(shè)計(jì)是借鑒于Smalltalk這門語(yǔ)言馍资。

SmalltalkM仓鳌!Objective-C的特性基本上是照搬的Smalltalk迷帜,看來(lái)Smalltalk里可以找到一些線索物舒。

Smalltalk-面向?qū)ο蟮那拜?/h3>

Smalltalk色洞,被公認(rèn)為歷史上第二個(gè)面向?qū)ο蟮恼Z(yǔ)言戏锹,其亮點(diǎn)是它的消息發(fā)送機(jī)制。

Smalltalk中的MetaClass的設(shè)計(jì)是Smalltalk-80加入的火诸。而之前的Smalltalk-76锦针,并不是每個(gè)類有一個(gè)MetaClass,而是所有類的isa指針都指向一個(gè)特殊的類置蜀,叫做Class(這種設(shè)計(jì)之后也被Java借鑒了)奈搜。

而每個(gè)類都有自己MetaClass的設(shè)計(jì),加入的原因是盯荤,因?yàn)镾malltalk里面馋吗,類是對(duì)象,而對(duì)象就可以響應(yīng)消息秋秤,那么類的消息的響應(yīng)的方法就應(yīng)該由類的類去存儲(chǔ)宏粤,而每個(gè)MetaClass就持有每個(gè)類的類方法脚翘。

問(wèn)題1:每個(gè)MetaClass的isa指針指向什么?

如果MetaClass再有MetaClass绍哎,那么這個(gè)關(guān)系將無(wú)窮無(wú)盡来农。Smalltalk里的解決方案是,指向同一個(gè)叫MetaClass的類崇堰。

問(wèn)題2:MetaClass的isa指針指向什么沃于?

指向他的實(shí)例,也就是實(shí)例的isa指向MetaClass海诲,同時(shí)MetaClassisa指向?qū)嵗庇ǎ嗷ブ钢?/p>

那么Smalltalk的繼承關(guān)系,其實(shí)和Objective-C的很像了(后面有class的是前者的MetaClass)特幔。

屏幕快照 2017-09-15 上12.40.10

這時(shí)候產(chǎn)生了一個(gè)重要的問(wèn)題蒋困,假如去掉MetaClass,把類方法放到也類里面是否可行敬辣?

這個(gè)問(wèn)題雪标,我思索許久,發(fā)現(xiàn)其實(shí)是一個(gè)對(duì)面向?qū)ο蟮恼軐W(xué)思想問(wèn)題溉跃,要對(duì)這個(gè)問(wèn)題下結(jié)論村刨,不得不重新講講面向?qū)ο蟆?/p>

從Smalltalk重新認(rèn)識(shí)面向?qū)ο?/h3>

以前談到面向?qū)ο螅倳?huì)提到撰茎,面向?qū)ο笕卣鳎?strong>封裝嵌牺、繼承、多態(tài)龄糊。但其實(shí)逆粹,面向?qū)ο笾幸卜至髋桑鏑++這種來(lái)自Simula的設(shè)計(jì)思想的炫惩,更注重的是類的劃分僻弹,因?yàn)榉椒ㄕ{(diào)用是靜態(tài)的。而如Objective-C這種借鑒Smalltalk的他嚷,更注重的是消息傳遞蹋绽,是動(dòng)態(tài)響應(yīng)消息。

而面向?qū)ο笕N特征筋蓖,更基于的是類的劃分而提出的卸耘。

這兩種思想最大的不同,我認(rèn)為是自上而下自下而上的思考方式粘咖。

  • 類的劃分蚣抗,要求類的設(shè)計(jì)者是以一個(gè)很高的層次去設(shè)計(jì)這個(gè)類,提取出類的特性和本質(zhì)瓮下,進(jìn)行類的構(gòu)建翰铡。知道類型才可以去發(fā)送消息給對(duì)象设哗。
  • 消息傳遞,要求的是類的設(shè)計(jì)者以消息為起點(diǎn)去構(gòu)建類两蟀,也就是對(duì)外界的變化進(jìn)行響應(yīng)网梢,而不關(guān)心自身的類型,設(shè)計(jì)接口赂毯。嘗試?yán)斫庀⒄铰玻瑹o(wú)法處理則進(jìn)行特殊處理。

在此不討論兩種方式的優(yōu)劣之分党涕,而著重講講Smalltalk這種設(shè)計(jì)烦感。

消息傳遞對(duì)于面向?qū)ο蟮脑O(shè)計(jì),其實(shí)在于給出一種對(duì)消息的解決方案膛堤。而面向?qū)ο髢?yōu)點(diǎn)之一的復(fù)用手趣,在這種設(shè)計(jì)里,更多在于復(fù)用解決方案肥荔,而不是單純的類本身绿渣。這種思想就如設(shè)計(jì)組件一般,關(guān)心接口燕耿,關(guān)心組合而非類本身中符。其實(shí)之所以有MetaClass這種設(shè)計(jì),我的理解并不是先有MetaClass誉帅,而是在萬(wàn)物都是對(duì)象的Smalltalk里淀散,向?qū)ο蟀l(fā)送消息的基本解決方案是統(tǒng)一的,希望復(fù)用的蚜锨。而實(shí)例和類之間用的這一套通過(guò)isa指針指向的Class單例中存儲(chǔ)方法列表和查詢方法的解決方案的流程档插,是應(yīng)該在類上復(fù)用的,而MetaClass就順理成章出現(xiàn)罷了亚再。

最后

回到一開(kāi)始那個(gè)問(wèn)題郭膛,為什么要設(shè)計(jì)MetaClass,去掉把類方法放到類里面行不行针余?

我的理解是饲鄙,可以,但不Smalltalk圆雁。這樣的設(shè)計(jì)是C++那種自上而下的設(shè)計(jì)方式,類方法也是類的一種特征描述帆谍。而Smalltalk的精髓正在于消息傳遞伪朽,復(fù)用消息傳遞才是根本目的,而MetaClass只不過(guò)是因此需要的一個(gè)工具罷了汛蝙。

PS:筆者這個(gè)問(wèn)題從MetaClass入手去思考烈涮,是百思不得其解的朴肺。后來(lái)看了很多面向?qū)ο蟮臇|西,才發(fā)現(xiàn)這不過(guò)是一個(gè)產(chǎn)物坚洽,而并不是一個(gè)重點(diǎn)戈稿。

PSS:對(duì)于類的實(shí)現(xiàn),Javascript中那種使用Protocol實(shí)現(xiàn)的方式也很有意思讶舰,受限于篇幅鞍盗,暫不展開(kāi)

有任何問(wèn)題歡迎評(píng)論私信或者提issue
QQ:757765420
Email:nemocdz@gmail.com
Github:Nemocdz
微博:@Nemocdz

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跳昼,一起剝皮案震驚了整個(gè)濱河市般甲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹅颊,老刑警劉巖敷存,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異堪伍,居然都是意外死亡锚烦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門帝雇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挽牢,“玉大人,你說(shuō)我怎么就攤上這事摊求∏莅危” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵室叉,是天一觀的道長(zhǎng)睹栖。 經(jīng)常有香客問(wèn)我,道長(zhǎng)茧痕,這世上最難降的妖魔是什么野来? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮踪旷,結(jié)果婚禮上曼氛,老公的妹妹穿的比我還像新娘。我一直安慰自己令野,他們只是感情好舀患,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著气破,像睡著了一般聊浅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天低匙,我揣著相機(jī)與錄音旷痕,去河邊找鬼。 笑死顽冶,一個(gè)胖子當(dāng)著我的面吹牛欺抗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播强重,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼绞呈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了竿屹?” 一聲冷哼從身側(cè)響起报强,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拱燃,沒(méi)想到半個(gè)月后秉溉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碗誉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年召嘶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哮缺。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弄跌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尝苇,到底是詐尸還是另有隱情铛只,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布糠溜,位于F島的核電站淳玩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏非竿。R本人自食惡果不足惜蜕着,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望红柱。 院中可真熱鬧承匣,春花似錦、人聲如沸锤悄。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)铁蹈。三九已至宽闲,卻和暖如春众眨,著一層夾襖步出監(jiān)牢的瞬間握牧,已是汗流浹背容诬。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沿腰,地道東北人览徒。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像颂龙,于是被迫代替她去往敵國(guó)和親习蓬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉措嵌,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評(píng)論 0 9
  • 首先說(shuō)明躲叼,這篇文章幾乎都是抄錄的別人的博客,簡(jiǎn)書文章企巢,在此總結(jié)枫慷,只是為了方便記憶和以后閱讀,如果有什么失禮的地方浪规,...
    LiYaoPeng閱讀 4,899評(píng)論 1 14
  • 已下大部分內(nèi)容參考于:Objective-C Runtime楊蕭玉的博客 Objective-C Runtime深...
    dyouknow閱讀 565評(píng)論 0 2
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 728評(píng)論 0 2
  • 原文出處:南峰子的技術(shù)博客 Objective-C語(yǔ)言是一門動(dòng)態(tài)語(yǔ)言或听,它將很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)期做的事放到了...
    _燴面_閱讀 1,215評(píng)論 1 5