天天看點

Objective-C 2.0 with Cocoa Foundation---NSObject的奧秘(3)

  圖6-2,選擇執行斷點   第六步,選擇Xcode上面的菜單的“Run”,然後選擇“Debuger” ,在Debuger視窗裡面選擇“Build and Go”。   好的,大家就停在這裡,不要做其他的操作,我們把程式中斷在程式幾乎執行到最後的斷點上,我們将要通過Debuger來看看objecsive-C内部究竟發生了什麼樣的奇妙的魔法。   注意   在從編譯到執行的過程當中,會出現一些警告。由于本章程式訓示用來闡述一些NSobjecs内部的東西,是以請忽略掉這些警告。當然,我們在寫自己的程式的時候,編譯産生的警告一般是不能被忽略的。   6.3,超類方法的調用   我們現在打開“06-NSobjecs.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所示:

Objective-C 2.0 with Cocoa Foundation---NSObject的奧秘(3)

    圖6-3,cattle_setLegsCount_IMP和redBull_setLegsCount_IMP的位址。   注意   由于環境和執行的時候的 記憶體情況不同,是以同學們的 電腦上顯示的位址的數值可能和圖6-3的數值不一樣。   他們的位址完全一樣,說明他們使用的是相同的代碼段。這種結果是怎樣産生的呢?大家請打開“MyNSobjecs.h”,參照下列代碼: struct my_objc_class {     

     meteClass           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_objecs_type;

};   筆者在這裡把開源代碼的名字的定義加上了“my_”字首,僅僅是為了使編譯不出現問題。這段代碼實際上就是Class的實際上定義的部分。   我們注意到這裡的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;

Objective-C 2.0 with Cocoa Foundation---NSObject的奧秘(3)

  好的,現在我們的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裡面也有超類的,那就是 NSobjecs。是以runtime會去NSobjecs裡面尋找。當然,NSobjecs不會神奇到可以預測我們要定義setLegsCount:所 以runtime是找不到的。   在這個時候,runtime 并沒有放棄最後的努力,再沒有找到對應的方法的時候,runtime會向對象發送一個forwardInvocation:的消息,并且把原始的消息以及 消息的參數打成一個NSInvocation的一個對象裡面,作為forwardInvocation:的唯一的參數。 forwardInvocation:本身是在NSobjecs裡面定義的,如果你需要重載這個函數的話,那麼任何試圖向你的類發送一個沒有定義的消息的 話,你都可以在forwardInvocation:裡面捕捉到,并且把消息送到某一個安全的地方,進而避免了系統報錯。   筆者沒有在本章代碼中重寫forwardInvocation:,但是在重寫forwardInvocation:的時候一定要注意避免消息的 循環發送。比如說,同學們在A類對象的forwardInvocation裡面,把A類不能響應的消息以及消息的參數發給B類的對象;同時在B類的 forwardInvocation裡面把B類不能響應的消息發給A類的時候,容易形成死循環。當然一個人寫代碼的時候不容易出現這個問題,當你在一個工 作小組裡面做的時候,如果你重寫forwardInvocation:的時候,需要和小組的其他人達成共識,進而避免循環調用。

轉載于:https://blog.51cto.com/mkhgg/654763

繼續閱讀