在個人理解來講: 對象(object)即一塊內存,本文要探討的是一個Objective-C對象在內存的佈局(layout)問題,水果的官方文檔有說,一個類(class)若是不須要從NSObject繼承其某些特定的行爲是不用繼承NSObject的,這裏我將討論限制在繼承了NSObject的類的對象範圍內。html
首先來看一下,NSObject的定義:程序員
1 @interface NSObject <NSObject> {
2 Class isa;
3 }
(因爲咱們討論的是內存佈局,所以將其方法的定義撇開)objective-c
在Objective-C中,@interface關鍵字能夠看着是C語言中的struct關鍵字的別名,固然他還會有一些其它功能,好比說讓編譯器知道@interface後後面的是一個Objective-C的類的名字等。但就咱們研究其內存佈局來講,咱們簡單地將其替換爲struct,並將protocal定義去掉。所以,NSObject的定義就是樣:佈局
1 struct NSObject{
2 Class isa;
3 }
那個這個Class又是什麼呢?在objc.h中咱們發現其僅僅是一個結構(struct)指針的typedef定義:ui
1 typedefstruct objc_class *Class;
所以,NSObject的定義就像這個樣子:this
1 struct NSObject{
2 objc_class *isa
3 }
isa就是「is a」,對於全部繼承了NSObject的類其對象也都有一個isa指針。這個isa指針指向的東西(先這樣稱呼它吧)就是關於這個對象所屬的類的定義。spa
刨根問底是咱們程序員的天性:那object_class的定義是什麼呢?因爲水果公司如今將這個定義隱藏起來了,不過我依然有辦法,用XCode隨便建一個工程,在某個變量定義處打個debug斷點,而後經過XCode的GUI或者用gdb的p命令查看其結構,這裏我使用gdb打印一個UINavigationController變量,咱們看到只是一個指針而已:debug
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是什麼呢?對它解引用再打印內容大體以下:code
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