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ǔ)言。

Smalltalk??!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ū)ο笾幸卜至髋?,如C++這種來(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)斫庀?,無(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容