)NSObject的奧秘

來源:互聯網
上載者:User
文章目錄
  • 6,NSObject的奧秘
Objective-C 2.0 with Cocoa Foundation--- 6,NSObject的奧秘6,NSObject的奧秘

本系列講座有著很強的前後相關性,如果你是第一次閱讀本篇文章,為了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裡。

在上一章裡面,筆者向大家介紹了在Objective-C裡面的幾個非常重要的概念, 簡單的說就是SEL,Class和IMP。我們知道Objective-C是C語言的擴充,有了這3個概念還有我們以前講過的繼承和封裝的概念,Objective-C發生了翻天覆地的變化,既相容C語言的高效特性又實現了物件導向的功能。

Objective-C從本質上來說,還是C語言的。那麼內部究竟是怎樣實現SEL,Class和IMP,還有封裝和繼承的?為瞭解答這個問題,筆者決定在本章向大家概要的介紹一下Objective-C的最主要的一個類,NSObject。

不過說實在話,如果同學們覺得本章的內容比較晦澀難懂的話,不閱讀本章的內容絲毫不會對寫程式產生任何不良的影響,但是如果掌握了本章的內容的話,對加深對Objective-C的理解,對於今後筆者將要講述的內容而言,將會是一個極大的促進。 

6.1,本章程式的執行結果

在本章裡面,我們將要繼續使用我們在前面幾章已經構築好的類Cattle和Bull。由於在現在的Xcode版本裡面,把一些重要的東西比如說Class的原型定義都放到了LIB檔案裡面,所以這些東西的具體的定義,對於我們來說是不可見的。

我們首先把第4章的代碼開啟,然後開啟“Cattle.h” 檔案,把滑鼠移動到“NSObject”上面,單擊滑鼠右鍵,在快顯功能表裡面選擇“Jump to Definition”。然後會彈出一個小菜單,我們選擇“interface NSObject” 。我們可以看到如下代碼

@interface NSObject <NSObject> {
   Class       isa;

我們知道了,所謂的NSObject裡面只有一個變數,就是Class類型的isa。isa的英文的意思就是is a pointer的意思。也就是說NSObject裡面只有一個執行個體變數isa。好的,我們需要知道Class是一個什麼東西,我們把滑鼠移動到“Class”上面,單擊滑鼠右鍵,在快顯功能表裡面選擇“Jump to Definition”,我們看到了如下的代碼:

typedef struct objc_class *Class;
typedef struct objc_object {
   Class isa;

} *id;

... 

我們在這裡知道了,Class實際上是一個objc_class的指標類型,我們把滑鼠移動到“objc_class”上面,單擊滑鼠右鍵,在快顯功能表裡面選擇“Jump to Definition”,發現我們還是在這個視窗裡面,Xcode並沒有把我們帶到objc_class的定義去,所以我們無從知道objc_class內部究竟是一個什麼樣的東西。

筆者順便提一下,大家也許注意到了id的定義,id實際上是objc_object結構的一個指標,裡面只有一個元素那就是Class。那麼根據上面我們看到的,所謂的id就是objc_class的指標的指標。讓我們回憶一下下面的代碼:

 

id cattle = [Cattle new];

這句話是在初始化和執行個體話cattle對象,這個過程,實際上可以理解為,runtime為我們初始化好了Class的指標,並且把這個指標返回給我們。我們初始化對象完成了之後,實際上我們得到的對象就是一個指向這個對象的Class指標。 

讓我們在回過頭來說說這個神秘的Class,我們無法在Xcode裡面看到Class也就是objc_class的定義。慶幸的是這部分的定義是GCC代碼,是開源的。筆者下載了開源的代碼之後,把開源的代碼作了一些小小的調整,然後把Class的定義等等放到了我們的工程檔案裡面去,通過類型轉化之後,我們終於可以看到Class,SEL,還有isa等等過去對我們來說比較“神秘”的東西的真正面目。

我們在前面幾章裡面在每一個章的第一節裡面都要介紹一下本章程式執行結果的螢幕拷貝,本章也是一樣,但是本章的執行結果非常簡單。因為對於本章而言重點應該是放在對NSObject機制的理解上。

 

圖6-1,本章程式運行結果

大家看到本章程式的運行結果的螢幕拷貝的時候,也許會覺得很無趣,因為單單從結果畫面,我們沒有發現任何令人感到很有興趣的東西,相反,都是同學們已經很熟悉的一些老面孔。但是本章所要講述的東西也許是同學們在其他語言裡面從來沒有遇到過的東西,這些東西將會令人感到新鮮和激動。

6.2,實現步驟

第一步,按照我們在第2章所述的方法,建立一個項目,項目的名字叫做06-NSObject。如果你是第一次看本篇文章,請到這裡參看第二章的內容。

第二步,按照我們在第4章的4.2節的第二,三,四步所述的方法,把在第4章已經使用過的“Cattle.h”,“Cattle.m”,“Bull.h”還有“Bull.m” 匯入本章的項目裡面。如果你沒有第4章的代碼,請到這裡下載。如果你沒有閱讀第4章的內容,請參看這裡。

第三步,把滑鼠移動到項目瀏覽器上面的“Source”上面,然後在彈出的菜單上面選擇“Add”,然後在子功能表裡面選擇“New File”,然後在建立檔案對話方塊的左側最下面選擇“Other”,然後在右側視窗選擇“Empty File”,選擇“Next”,在“New File”對話方塊裡面的“File Name”欄內輸入“MyNSObject.h”。然後輸入(或者是拷貝也可以,因為這是C的代碼,如果你很熟悉C語言的話,可以拷貝一下節省時間)如下代碼:

#include <stddef.h>
typedef const struct objc_selector 
{
    void *sel_id;
    const char *sel_types;
} *MySEL;
typedef struct my_objc_object {
    struct my_objc_class*  class_pointer;
} *myId;
    
typedef myId (*MyIMP)(myId, MySEL, ); 
    
typedef char *STR;                              /* String alias */
    
typedef struct my_objc_class *MetaClass;
typedef struct my_objc_class *MyClass;
struct my_objc_class {     
    MetaClass           class_pointer;         
    struct my_objc_class*  super_class;            
    const char*         name;                 
    long                version;               
    unsigned long       info;                  
    long                instance_size;          
    struct objc_ivar_list* ivars;              
    struct objc_method_list*  methods;          
    struct sarray *    dtable;                  
    struct my_objc_class* subclass_list;           
    struct my_objc_class* sibling_class;
    struct objc_protocol_list *protocols;         
    void* gc_object_type;
};
    
typedef struct objc_protocol {
    struct my_objc_class* class_pointer;
    char *protocol_name;
    struct objc_protocol_list *protocol_list;
    struct objc_method_description_list *instance_methods, *class_methods; 
} Protocol; 
    
    
typedef void* retval_t;        
typedef void(*apply_t)(void);    
typedef union arglist {
    char *arg_ptr;
    char arg_regs[sizeof (char*)];
} *arglist_t;            

typedef struct objc_ivar* Ivar_t;
typedef struct objc_ivar_list {
    int   ivar_count;                             
    struct objc_ivar {
        const char* ivar_name;                    
        const char* ivar_type;                      
        int        ivar_offset;                           
    } ivar_list[1];                              
} IvarList, *IvarList_t;

typedef struct objc_method {
    MySEL         method_name;                  
    const char* method_types;                 
    MyIMP         method_imp;                  
} Method, *Method_t;

typedef struct objc_method_list {
    struct objc_method_list*  method_next;   
    int            method_count;              
    Method method_list[1];                   
} MethodList, *MethodList_t;

struct objc_protocol_list {
    struct objc_protocol_list *next;
    size_t count;
    Protocol *list[1];
};

第四步,開啟06-NSObject.m檔案,輸入如下代碼並且儲存

#import <Foundation/Foundation.h>
#import "Cattle.h"
#import "Bull.h"
#import "MyNSObject.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    id cattle = [Cattle new];    
    id redBull = [Bull new];
    SEL setLegsCount_SEL = @selector(setLegsCount:);
    IMP cattle_setLegsCount_IMP = [cattle methodForSelector:setLegsCount_SEL];
    IMP redBull_setLegsCount_IMP = [redBull methodForSelector:setLegsCount_SEL];
    
    [cattle setLegsCount:4];
    [redBull setLegsCount:4];
    [redBull setSkinColor:@"red"];
    
    Class cattle_class = cattle->isa;
    MyClass my_cattle_class = cattle->isa;
    SEL say = @selector(saySomething);
    IMP cattle_sayFunc = [cattle methodForSelector:say];
    cattle_sayFunc(cattle, say);
    
    Class redBull_class = redBull->isa;
    MyClass my_redBull_class = redBull->isa;

    IMP redBull_sayFunc = [redBull methodForSelector:say];
    redBull_sayFunc(redBull, say);

    [pool drain];
    return 0;
}

 

第五步,在06-NSObject.m檔案的視窗的“[pool drain];”代碼的左側單擊一下視窗的邊框,確認一下是否出現一個藍色的小棒棒,如果有的話那麼斷點被選擇好了。6-2所示

 

 圖6-2,選擇執行斷點

第六步,選擇Xcode上面的菜單的“Run”,然後選擇“Debuger” ,在Debuger視窗裡面選擇“Build and Go”。

好的,大家就停在這裡,不要做其他的操作,我們把程式中斷在程式幾乎執行到最後的斷點上,我們將要通過Debuger來看看Objective-C內部究竟發生了什麼樣的奇妙的魔法。

 

注意

在從編譯到執行的過程當中,會出現一些警告。由於本章程式指示用來闡述一些NSObject內部的東西,所以請忽略掉這些警告。當然,我們在寫自己的程式的時候,編譯產生的警告一般是不能被忽略的。

6.3,超類方法的調用

我們現在開啟“06-NSObject.m”檔案,發現下面的代碼:

SEL setLegsCount_SEL = @selector(setLegsCount:);
IMP cattle_setLegsCount_IMP = [cattle methodForSelector:setLegsCount_SEL];
IMP redBull_setLegsCount_IMP = [redBull methodForSelector:setLegsCount_SEL];

這一段代碼,對同學們來說不是什麼新鮮的內容了,我們在第5章裡面已經講過,這個是SEL和IMP的概念。我們在這裡取得了cattle對象和redBull對象的setLegsCount:的函數指標。

如果大家現在已經不在Debuger裡面的話,那麼請選擇Xcode菜單裡面的,“Run”然後選擇“Debuger” 。

我們注意到在Debuger裡面,cattle_setLegsCount_IMP的地址和redBull_setLegsCount_IMP是完全一樣的,6-3所示:

圖6-3,cattle_setLegsCount_IMP和redBull_setLegsCount_IMP的地址。

注意

由於環境和執行的時候的記憶體情況不同,所以同學們的電腦上顯示的地址的數值可能和圖6-3的數值不一樣。

 

cattle_setLegsCount_IMP和redBull_setLegsCount_IMP的地址完全一樣,說明他們使用的是相同的程式碼片段。這種結果是怎樣產生的呢?大家請開啟“MyNSObject.h”,參照下列代碼:

struct my_objc_class {     
    MetaClass           class_pointer;         
    struct my_objc_class*  super_class;            
    const char*         name;                 
    long                version;               
    unsigned long       info;                  
    long                instance_size;          
    struct objc_ivar_list* ivars;              
    struct objc_method_list*  methods;          
    struct sarray *    dtable;                  
    struct my_objc_class* subclass_list;           
    struct my_objc_class* sibling_class;
    struct objc_protocol_list *protocols;         
    void* gc_object_type;
};

筆者在這裡把開原始碼的名字的定義加上了“my_”首碼,僅僅是為了區分一下。“MyNSObject.h”裡面的代碼問題很多,筆者從來沒有也不會在實際的代碼裡面使用這段代碼,使用這些代碼的主要目的是為了向大家講解概念,請大家忽略掉代碼裡面的種種問題。 

我們注意到這裡的methods變數,裡麵包存的就是類的方法名字(SEL)定義,方法的指標地址(IMP)。當我們執行

IMP cattle_setLegsCount_IMP = [cattle methodForSelector:setLegsCount_SEL];

的時候,runtime會通過dtable這個數組,快速的尋找到我們需要的函數指標,尋找函數的定義如下:

__inline__ IMP
objc_msg_lookup(id receiver, SEL op)
{
 if(receiver)
   return sarray_get(receiver->class_pointer->dtable, (sidx)op);
 else
   return nil_method;

 

好的,現在我們的cattle_setLegsCount_IMP沒有問題了,那麼redBull_setLegsCount_IMP怎麼辦?在Bull類裡面我們並沒有定義執行個體方法setLegsCount:,所以在Bull的Class裡面,runtime難道找不到setLegsCount:嗎?答案是,是的runtime直接找不到,因為我們在Bull類裡面根本就沒有定義setLegsCount:。

但是,從結果上來看很明顯runtime聰明的找到了setLegsCount:的地址,runtime是怎樣找到的?答案就在:

struct my_objc_class*  super_class;            

 

在自己的類裡面沒有找到的話,runtime會去Bull類的超類cattle裡面去尋找,慶幸的是它成功的在cattle類裡面runtime找到了setLegsCount:的執行地址入口,所以我們得到了redBull_setLegsCount_IMP。redBull_setLegsCount_IMP和cattle_setLegsCount_IMP都是在Cattle類裡面定義的,所以他們的代碼的地址也是完全一樣的。

我們現在假設,如果runtime在cattle裡面也找不到setLegsCount:呢?沒有關係,cattle裡面也有超類的,那就是NSObject。所以runtime會去NSObject裡面尋找。當然,NSObject不會神奇到可以預測我們要定義setLegsCount:所以runtime是找不到的。

在這個時候,runtime 並沒有放棄最後的努力,再沒有找到對應的方法的時候,runtime會向對象發送一個forwardInvocation:的訊息,並且把原始的訊息以及訊息的參數打成一個NSInvocation的一個對象裡面,作為forwardInvocation:的唯一的參數。 forwardInvocation:本身是在NSObject裡面定義的,如果你需要重載這個函數的話,那麼任何試圖向你的類發送一個沒有定義的訊息的話,你都可以在forwardInvocation:裡面捕捉到,並且把訊息送到某一個安全的地方,從而避免了系統報錯。

筆者沒有在本章代碼中重寫forwardInvocation:,但是在重寫forwardInvocation:的時候一定要注意避免訊息的迴圈發送。比如說,同學們在A類對象的forwardInvocation裡面,把A類不能響應的訊息以及訊息的參數發給B類的對象;同時在B類的forwardInvocation裡面把B類不能響應的訊息發給A類的時候,容易形成死迴圈。當然一個人寫代碼的時候不容易出現這個問題,當你在一個工作小組裡面做的時候,如果你重寫forwardInvocation:的時候,需要和小組的其他人達成共識,從而避免迴圈調用。

6.4,重載方法的調用

讓我們繼續關注“06-NSObject.m”檔案,請大家參考一下下面的代碼:

 1 Class cattle_class = cattle->isa;
 2 MyClass my_cattle_class = cattle->isa;
 3 SEL say = @selector(saySomething);
 4 IMP cattle_sayFunc = [cattle methodForSelector:say];
 5 cattle_sayFunc(cattle, say);
 6     
 7 Class redBull_class = redBull->isa;
 8 MyClass my_redBull_class = redBull->isa;
 9 
10 IMP redBull_sayFunc = [redBull methodForSelector:say];
11 redBull_sayFunc(redBull, say);

本節的內容和6.3節的內容比較類似,關於代碼部分筆者認為就不需要解釋了,如果同學們有所不熟悉的話,可以參考一下第5章的內容。

在我們的Cattle類和Bull類裡面,都有saySometing這個執行個體方法。我們知道只要方法的定義相同,那麼它們的SEL是完全一樣的。我們根據一個SEL say,在cattle和redBull對象裡面找到了他們的函數指標。根據6.3節的講述,我們知道當runtime接收到尋找方法的時候,會首先在這個類裡面尋找,尋找到了之後尋找的過程也就結束了,同時把這個方法的IMP返回給我們。所以,在上面的代碼裡面的cattle_sayFunc和redBull_sayFunc應該是不一樣的,6-4所示:

 

圖6-4, cattle_sayFunc和redBull_sayFunc的地址

6.5,超類和子類中的Class 

在類進行記憶體配置的時候,對於一個類而言,runtime需要找到這個類的超類,然後把超類的Class的指標的地址賦值給isa裡面的super_class。所以,我們的cattle裡面的Class應該和redBull裡面的Class裡面的super_class應該是完全相同的,請參照圖6-5:

 

圖6-5, cattle裡面的Class和redBull裡面的Class裡面的super_class

6.6,執行個體變數的記憶體配置的位置

我們先來回憶一下對象是怎樣被建立的。建立對象的時候,類的內容需要被調入到記憶體當中我們稱之為記憶體配置(Allocation),然後需要把實體變數進行初始化(Initialization),當這些步驟都結束了之後,我們的類就被執行個體化了,我們把執行個體化完成的類叫做對象(Object)。

對於記憶體配置的過程,runtime需要知道分配多少記憶體還有各個執行個體變數的位置。我們回到“MyNSObject.h”,參照如下代碼:

 

1 typedef struct objc_ivar* Ivar_t;
2 typedef struct objc_ivar_list {
3     int   ivar_count;                             
4     struct objc_ivar {
5         const char* ivar_name;                    
6         const char* ivar_type;                      
7         int        ivar_offset;                           
8     } ivar_list[1];                              
9 } IvarList, *IvarList_t;

我們仔細看看第5行的ivar_name,顧名思義這個是執行個體變數的名字,第6行的ivar_type是執行個體變數的類型,第7行的ivar_offset,這個就是位置的定義。runtime從類的isa裡面取得了這些資訊之後就知道了如何去分配記憶體。我們來看看圖6-6:

 

圖6-6,執行個體變數在記憶體中的位置

在cattle裡面,我們看到了第一個執行個體變數是isa,第二個就是我們定義的legsCount。其中isa是超類的變數,legsCount是Cattle類的變數。我們可以看出來,總是把超類的變數放在前頭,然後是子類的變數。

那麼對於redBull而言是什麼樣子呢?我們來看看圖6-7

圖6-7,redBull裡面的執行個體變數的位置

我們通過圖6-7可以發現redBull的Class裡面的skinColor的位置位移是8,很明顯,runtime為isa和legsCount預留了2個位置。

6.7本章總結

非常感謝大家!

在本章裡面,筆者通過一個小小的“把戲”為同學們揭開了NSObject的神秘的面紗。本章的內容,雖然對理解Objective-C不是必需的,但是對以後的章節的內容的理解會有一個非常好的輔助作用,希望同學們花費一點點心思和時間閱讀一下。

另外,筆者需要再次強調一下,由於筆者沒有得到官方的正式的文檔說明,所以不能保證本章的內容是完整而且準確的,本章內容僅供大家參考和娛樂,希望大家諒解。

 

 

 posted @ 2009-04-13 11:41 Yaski 閱讀(7906) 評論(18) 編輯

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.