轉自:http://www.cnblogs.com/csutanyu/archive/2011/12/12/Objective-C_memory_layout.html
在我的理解來說: 對象(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