前置知識
首先簡單分析下在Objective-C中,對象是什麼。下面源碼基於Runtime-709分析。
typedef struct objc_object *id;//id其實是一個object結構體的指針,所以id不用加*
typedef struct objc_class *Class;//Class是class結構體的指針
struct objc_object {
Class isa;
};
struct objc_class : objc_object {
Class superclass;
cache_t cache; // 用來緩存指針和虛函數表
class_data_bits_t bits; //方法列表等
//...
}
可以看到,對象最基本的就是有一個isa指針,指向他的class,而Class本身是繼承自object。isa指針的理解誒就是英文is a,代表“xxx is a (class)”。那麼也就是說,一個對象的isa指向哪個class,代表它是那個類的對象。那麼對於class來說,它也是一個對象,它的isa指針指向什麼呢?
對於Class來說,也就需要一個描述他的類,也就是“類的類”,而meta正是“關於某事自身的某事”的解釋,所以MetaClass就因此而生了。
而從runtime動態生成一個類的Api的方法中,我們也可以發現metaClass的蹤跡。
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes)
{
Class cls, meta;
rwlock_writer_t lock(runtimeLock);
// 如果 Class 名字已存在或父類沒有通過認證則創建失敗
if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) {
return nil;
}
//分配空間
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);
//構建meta和class的關係
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
通過這個方法生成後,就成了大家熟悉的那張圖。
從這張圖上,我們可以看到通過這麼一層繼承關係,Objective-C的對象原型繼承鏈就完整了。
同時,實例的實例方法函數存在類結構體中,類方法函數存在metaclass結構體中,而Objective-C的方法調用(消息)就會根據對象去找isa指針指向的Class對象中的方法列表找到對應的方法。
Python中的MetaClass
再講Objective-C之前,先講講別的語言的設計,通過各種語言的比較,可以從更廣的層面去理解語言的設計思想。而之所以先講起Python,是因爲我在搜索MetaClass時,搜索結果中大部分其實是講Python中MetaClass的。
先看看Python中一個對象結構是怎麼樣的,以下源碼基於CPython 3.7.0 alpha 1。
//object.h
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;//引用計數
struct _typeobject *ob_type;//類型
} PyObject;
和Objective-C中類似,ob_type其實就是一個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; //對象長度
} PyVarObject;
PyVarObject是一種可變長度對象,是在PyObject基礎上加上了對象的長度。而開始的內存包括了ob_base這個PyObject,就代表可以用PyObject指針進行引用。所以可以說,結構體中剛開始的部分是一個PyObject對象,在Python中引用就是一個對象。那麼PyTypeObject開頭是一個PyVarObject,也就是一個對象。也就是說,Python裏的Class,也是一個對象。
#在python中生成一個Class
MyClass = type('MyClass', (), {})
先看看Python裏面的type關鍵字是什麼。
//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 */
//....
};
可以發現type關鍵字是PyType_Type的一個引用,而PyType_Type是返回一個PyTypeObject,生成類的對象。而PyVarObject_HEAD_INIT遞歸引用了自己(PyType_Type)作爲它的type,所以可以得知type(class) == type
。也就是說,Python中類的isa指針指向type,也就說type其實就是MetaClass,而同時type(type) == type
,也就是type的isa指針指向type自身。那麼Python的對象鏈就如下圖。
而Objective-C不太一樣的是,並不是每一個類都有一個MetaClass,而是所有的類默認都是同一個MetaClass。當然,Python裏可以自定義新的MetaClass。
Python中爲何要使用元類的原因可能是,Python希望讓使用者對類有着最高的控制權,可以通過對元類的自定義而改變製造類的過程(例如Django裏的ORM)。也就是,Python開放了面向對象中類的製造者的權限。而同時,根據StackOverFlow這個問答,Python的類的設計是借鑑於Smalltalk這門語言。
Smalltalk!!Objective-C的特性基本上是照搬的Smalltalk,看來Smalltalk裏可以找到一些線索。
Smalltalk-面向對象的前輩
Smalltalk,被公認爲歷史上第二個面向對象的語言,其亮點是它的消息發送機制。
Smalltalk中的MetaClass的設計是Smalltalk-80加入的。而之前的Smalltalk-76,並不是每個類有一個MetaClass,而是所有類的isa指針都指向一個特殊的類,叫做Class
(這種設計之後也被Java借鑑了)。
而每個類都有自己MetaClass的設計,加入的原因是,因爲Smalltalk裏面,類是對象,而對象就可以響應消息,那麼類的消息的響應的方法就應該由類的類去存儲,而每個MetaClass就持有每個類的類方法。
問題1:每個MetaClass的isa指針指向什麼?
如果MetaClass再有MetaClass,那麼這個關係將無窮無盡。Smalltalk裏的解決方案是,指向同一個叫MetaClass
的類。
問題2:MetaClass
的isa指針指向什麼?
指向他的實例,也就是實例的isa指向MetaClass
,同時MetaClass
isa指向實例,相互指着。
那麼Smalltalk的繼承關係,其實和Objective-C的很像了(後面有class的是前者的MetaClass)。
這時候產生了一個重要的問題,假如去掉MetaClass,把類方法放到也類裏面是否可行?
這個問題,我思索許久,發現其實是一個對面向對象的哲學思想問題,要對這個問題下結論,不得不重新講講面向對象。
從Smalltalk重新認識面向對象
以前談到面向對象,總會提到,面向對象三特徵:封裝、繼承、多態。但其實,面向對象中也分流派,如C++這種來自Simula的設計思想的,更注重的是類的劃分,因爲方法調用是靜態的。而如Objective-C這種借鑑Smalltalk的,更注重的是消息傳遞,是動態響應消息。
而面向對象三種特徵,更基於的是類的劃分而提出的。
這兩種思想最大的不同,我認爲是自上而下和自下而上的思考方式。
- 類的劃分,要求類的設計者是以一個很高的層次去設計這個類,提取出類的特性和本質,進行類的構建。知道類型纔可以去發送消息給對象。
- 消息傳遞,要求的是類的設計者以消息爲起點去構建類,也就是對外界的變化進行響應,而不關心自身的類型,設計接口。嘗試理解消息,無法處理則進行特殊處理。
在此不討論兩種方式的優劣之分,而着重講講Smalltalk這種設計。
消息傳遞對於面向對象的設計,其實在於給出一種對消息的解決方案。而面向對象優點之一的複用,在這種設計裏,更多在於複用解決方案,而不是單純的類本身。這種思想就如設計組件一般,關心接口,關心組合而非類本身。其實之所以有MetaClass這種設計,我的理解並不是先有MetaClass,而是在萬物都是對象的Smalltalk裏,向對象發送消息的基本解決方案是統一的,希望複用的。而實例和類之間用的這一套通過isa指針指向的Class單例中存儲方法列表和查詢方法的解決方案的流程,是應該在類上覆用的,而MetaClass就順理成章出現罷了。
最後
回到一開始那個問題,爲什麼要設計MetaClass,去掉把類方法放到類裏面行不行?
我的理解是,可以,但不Smalltalk。這樣的設計是C++那種自上而下的設計方式,類方法也是類的一種特徵描述。而Smalltalk的精髓正在於消息傳遞,複用消息傳遞纔是根本目的,而MetaClass只不過是因此需要的一個工具罷了。
PS:筆者這個問題從MetaClass入手去思考,是百思不得其解的。後來看了很多面向對象的東西,才發現這不過是一個產物,而並不是一個重點。
PSS:對於類的實現,Javascript中那種使用Protocol實現的方式也很有意思,受限於篇幅,暫不展開
更多:iOS面試題大全
更多:《BAT面試答案文集.PDF》,獲取可加iOS技術交流圈:937194184。
收錄:原文地址