在我的理解來說: 對象(object)即一塊內存,本文要探討的是一個Objective-C對象在內存的布局(layout)問題,水果的官方文檔有說,一個類(class)如果不需要從NSObject繼承其某些特定的行為是不用繼承NSObject的,這里我將討論限制在繼承了NSObject的類的對象范圍內。
首先來看一下,NSObject的定義:
1 @interface NSObject <NSObject> {
2 Class isa;
3 }
(由於我們討論的是內存布局,因此將其方法的定義撇開)
在Objective-C中,@interface關鍵字可以看着是C語言中的struct關鍵字的別名,當然他還會有一些其它功能,比如說讓編譯器知道@interface后后面的是一個Objective-C的類的名字等。但就我們研究其內存布局來說,我們簡單地將其替換為struct,並將protocal定義去掉。因此,NSObject的定義就是樣:
1 struct NSObject{
2 Class isa;
3 }
那個這個Class又是什么呢?在objc.h中我們發現其僅僅是一個結構(struct)指針的typedef定義:
1 typedefstruct objc_class *Class;
因此,NSObject的定義就像這個樣子:
1 struct NSObject{
2 objc_class *isa
3 }
isa就是“is a”,對於所有繼承了NSObject的類其對象也都有一個isa指針。這個isa指針指向的東西(先這樣稱呼它吧)就是關於這個對象所屬的類的定義。
刨根問底是我們程序員的天性:那object_class的定義是什么呢?由於水果公司現在將這個定義隱藏起來了,不過我依然有辦法,用XCode隨便建一個工程,在某個變量定義處打個debug斷點,然后通過XCode的GUI或者用gdb的p命令查看其結構,這里我使用gdb打印一個UINavigationController變量,我們看到只是一個指針而已:
1 (gdb) p dialUNC
2
3 $1 = (UINavigationController *) 0x8e8be80
對指針解引用再打印,我們發現里面有很多很多東西,大致如下(由於gdb打印出來內容太多,省略號表示省略了一些內容):
1 (gdb) p *dialUNC
2 $1 = {
3 <UIViewController> = {
4 <UIResponder> = {
5 <NSObject> = {
6 isa = 0x1bebc1c
7 }, <No data fields>},
8 members of UIViewController:
9 _view = 0xd5dab60,
10 _tabBarItem = 0x0,
11 _navigationItem = 0x0,
12 _toolbarItems = 0x0,
13 _title = 0x0,
14 _nibName = 0x0,
15 ......(此處省略若干成員,課蜜黃蜂注)
16 },
17 members of UINavigationController:
18 _containerView = 0xd5dab60,
19 _navigationBar = 0xd5dad40,
20 _navigationBarClass = 0x1beb4d8,
21 _toolbar = 0x0,
22 _navigationTransitionView = 0xd5d2f10,
23 _currentScrollContentInsetDelta = {
24 top = 0,
25 left = 0,
26 bottom = 0,
27 right = 0
28 },
29 _previousScrollContentInsetDelta = {
30 top = 0,
31 left = 0,
32 bottom = 0,
33 right = 0
34 },
35 ......(此處省略若干成員,課蜜黃蜂注)
36 }
37 }
注意gdb打印結果中的黑體字,從中我們可以看到,UINavigationController內存中先是存放了父類的實例變量再存放子類的實例變量。最前面的那個isa指針就是在NSObject中所定義的。由於Objective-C中沒有多繼承,因此其內存布局還是很簡單的,就是:最前面有個isa指針,然后父類的實例變量存放在子類的成員變量之前,so easy!!!但還有一個問題,我們很好奇,這個isa是什么呢?對它解引用再打印內容大致如下:
1 (gdb) p *dialUNC->isa
2 $2 = {
3 isa = 0x1bebc30,
4 super_class = 0x1bebba4,
5 name = 0xd5dd8d0 "?",
6 version = 45024840,
7 info = 223886032,
8 instance_size = 43102048,
9 ivars = 0x1bebb7c,
10 methodLists = 0xd5dab10,
11 cache = 0x2af0648,
12 protocols = 0xd584050
13 }
這就是一個Class或者說objc_class結構在內存中的樣子。其實在Objective-C2.0之前這個結構的定義是暴露給用戶的,但在Objective-C2.0中,水果公司將它隱藏起來了。經過在網上的查找,發現在Objective-C2.0之前其定義大致如下:
1 struct objc_class {
2 Class isa;
3
4 Class super_class;
5
6 const char *name;
7
8 long version;
9 long info;
10
11 long instance_size;
12 struct objc_ivar_list *ivars;
13 struct objc_method_list **methodLists;
14
15 struct objc_cache *cache;
16 struct objc_protocol_list *protocols;
17 }
因此簡單地說,一個objc_class對象包括一個類的:父類定義(super_class), 變量列表,方法列表,還有實現了哪些協議(Protocal)等等。
"等一下",有人要喊了,"我們剛才在說一個對象里面有一個isa指針,這個指針的定義是objc_class,腫么這個objc-class中還有一個isa?"
在這里有必要跟大家啰嗦一大段文字了:在Objective-C中任何的類定義都是對象。即在程序啟動的時候任何類定義都對應於一塊內存。在編譯的時候,編譯器會給每一個類生成一個且只生成一個”描述其定義的對象”,也就是水果公司說的類對象(class object),他是一個單例(singleton), 而我們在C++等語言中所謂的對象,叫做實例對象(instance object)。對於實例對象我們不難理解,但類對象(class object)是干什么吃的呢?我們知道Objective-C是門很動態的語言,因此程序里的所有實例對象(instace objec)都是在運行時由Objective-C的運行時庫生成的,而這個類對象(class object)就是運行時庫用來創建實例對象(instance object)的依據。
讓我們來理一下,到目前為止,我們知道了:任何直接或間接繼承了NSObject的類,它的實例對象(instacne objec)中都有一個isa指針,指向它的類對象(class object)。這個類對象(class object)中存儲了關於這個實例對象(instace object)所屬的類的定義的一切:包括變量,方法,遵守的協議等等。
再回到之前的問題,腫么這個實例對象(instance object)的isa指針指向的類對象(class object)里面還有一個isa呢?
這個類對象(class objec)的isa指向的依然是一個objc-class,它就是“元類對象”(metaclass object),它和類對象(class object)的關系是這樣的: 類對象(class object)中包含了類的實例變量,實例方法的定義,而元類對象(metaclass object)中包括了類的類方法(也就是C++中的靜態方法)的定義。類對象和元類對象中水果公司當然還會包含一些其它的東西,以后也可能添加其它的內容,但對於我們了解其內存布局來說,只需要記住:類對象存的是關於實例對象的信息(變量,實例方法等),而元類對象(metaclass object)中存儲的是關於類的信息(類的版本,名字,類方法等)。要注意的是,類對象(class object)和元類對象(metaclass object)的定義都是objc_class結構,其不同僅僅是在用途上,比如其中的方法列表在類對象(instance object)中保存的是實例方法(instance method),而在元類對象(metaclass object)中則保存的是類方法(class method)。關於元類對象水果官方文檔" The Objective-‐C Programming Language "P29頁頂部描述如下:
Note: The compiler also builds a metaclass object for each class. It describes the class object just as the class object describes instances of the class. But while you can send messages to instances and to the class object, the metaclass object is used only internally by the runtime system.
這一大段文字好像有點繞,那我們來看一個例子。下面我以一個有4層繼承關系的類的實例變量的內存布局為例。繼承關系如下:

通過打印D3類的一個實例變量並將那些isa,super_class的地地址記錄下來整理得到的關系如下圖:
在這里對上圖進行一下解釋: 矩形表示對象(object),即一塊內存;箭頭表示指針,isa即isa指針,super表示super_class指針,這些指針是箭頭尾部對象(object)的成員變量,除了“D3實例對象”(最左邊的對象),其它對象都是在程序一啟動就創建在在內存中的了而且都是單例(singleton),類對象(class object)和元類對象(metaclass object)只是用途不一樣,其定義都為objc_class結構。
D3對象的內存布局為:從前往后為isa,D1的實例變量,D2的實例變量,D3的實例變量。而isa指針指向的內容就是上圖中的“D3類對象”。對於上圖,任何類C如果直接或間接繼承NSObject 或者其就是NSObject,則有如下結論:
1. 類C的類對象(class object)的super_class都指向了類C父類的類對象(class object), NSObject的類對像的super_class指向0x0
2. 類C的類對象(class object)的isa指針都指向他的元類對象(metaclass object)
3. 類C的元類對象(metaclass object)的super_class指針指向父類的元類對象(metaclass object), 例外:NSObject的元類對象(metaclass object)的super_class指向NSObject的類對象(class object).
4. 類C的元類對象(metaclass object)的isa指針指都指向NSObject的元類對象(metaclass object)
NSObject的實例對象(雖然它沒有實例變量和實例方法但這個對象仍然存在)其super_class指向地址0x0,因為NSObject沒有父類, 這滿足上面的結論1。
NSObject的實例對象的isa指向了NSObject的元類對象(metaclass object),這滿足上面結論2。
NSObject的元類對象(metaclass object)指向了自己,這也滿足上面結論4。
但NSObject的元類對象(metaclass object)的super_class指向了NSObject的類對象(class object),我沒有看出什么規律可言或者蘋果為什么要這樣做,我只能說“Apple just do this, I don't know why”(如果有人知道,麻煩告訴我一下,多謝)。我認為水果的工程師們只是簡單地又將它指向NSObject的類對象(class object),其實我認為這個super_class指針賦0x0也未嘗不可(這樣就滿足上面的結論3, 因為NSObject沒有父類,所以它的metaclass object的super_class指向0x0,我覺得這樣更統一。當然這只是我的yy罷了)。
到此結束,歡迎大家拍磚。
參考文獻: 1. Apple官方文檔" The Objective-‐C Programming Language "
2. http://algorithm.com.au/downloads/talks/objective-c-internals/objective-c-internals.pdf
轉:http://www.cnblogs.com/csutanyu/archive/2011/12/12/Objective-C_memory_layout.html