• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            羅朝輝(飄飄白云)

            關(guān)注嵌入式操作系統(tǒng),移動(dòng)平臺(tái),圖形開(kāi)發(fā)。-->加微博 ^_^

              C++博客 :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
              85 隨筆 :: 0 文章 :: 169 評(píng)論 :: 0 Trackbacks

            深入淺出ObjC消息   

            羅朝輝 (http://ww.cppblog.com/kesalin)

            轉(zhuǎn)載請(qǐng)注明出處

            在入門級(jí)別的ObjC 教程中,我們常對(duì)從C++或Java 或其他面向?qū)ο笳Z(yǔ)言轉(zhuǎn)過(guò)來(lái)的程序員說(shuō),ObjC 中的方法調(diào)用(ObjC中的術(shù)語(yǔ)為消息)跟其他語(yǔ)言中的方法調(diào)用差不多,只是形式有些不同而已。

            譬如C++ 中的:

            Bird * aBird = new Bird();

            aBird->fly();

            在ObjC 中則如下:

            Bird * aBird = [[Bird alloc] init];

            [aBird fly];


            乍看起來(lái),好像只是書寫形式不同而已,實(shí)則差異大矣。C++中的方法調(diào)用可能是動(dòng)態(tài)的,也可能是靜態(tài)的;而ObjC中的消息都為動(dòng)態(tài)的。下文將詳細(xì)介紹為什么是動(dòng)態(tài)的,以及編譯器在這背后做了些什么事情。


            要說(shuō)清楚消息這個(gè)話題,我們必須先來(lái)了解三個(gè)概念Class, SEL, IMP,它們?cè)趏bjc/objc.h 中定義:

            typedef struct objc_class *Class;

            typedef struct objc_object {

                Class isa;

            } *id;

             

            typedef struct objc_selector   *SEL;   

            typedef id (*IMP)(id, SEL, ...);

             

            Class 的含義

            Class 被定義為一個(gè)指向 objc_class的結(jié)構(gòu)體指針,這個(gè)結(jié)構(gòu)體表示每一個(gè)類的類結(jié)構(gòu)。而 objc_class 在objc/objc_class.h中定義如下:

            struct objc_class {

                struct objc_class super_class;  /*父類*/

                const char *name;                         /*類名字*/

                long version;                                  /*版本信息*/

                long info;                                        /*類信息*/

                long instance_size;                      /*實(shí)例大小*/

                struct objc_ivar_list *ivars;          /*實(shí)例參數(shù)鏈表*/

                struct objc_method_list **methodLists;  /*方法鏈表*/

                struct objc_cache *cache;                    /*方法緩存*/

                struct objc_protocol_list *protocols;   /*協(xié)議鏈表*/

            };

             

            由此可見(jiàn),Class 是指向類結(jié)構(gòu)體的指針,該類結(jié)構(gòu)體含有一個(gè)指向其父類類結(jié)構(gòu)的指針,該類方法的鏈表,該類方法的緩存以及其他必要信息。

            NSObject 的class 方法就返回這樣一個(gè)指向其類結(jié)構(gòu)的指針。每一個(gè)類實(shí)例對(duì)象的第一個(gè)實(shí)例變量是一個(gè)指向該對(duì)象的類結(jié)構(gòu)的指針,叫做isa。通過(guò)該指針,對(duì)象可以訪問(wèn)它對(duì)應(yīng)的類以及相應(yīng)的父類。如圖一所示:


             

            如圖一所示,圓形所代表的實(shí)例對(duì)象的第一個(gè)實(shí)例變量為 isa,它指向該類的類結(jié)構(gòu) The object’s class。而該類結(jié)構(gòu)有一個(gè)指向其父類類結(jié)構(gòu)的指針superclass, 以及自身消息名稱(selector)/實(shí)現(xiàn)地址(address)的方法鏈表。

             

            方法的含義:

            注意這里所說(shuō)的方法鏈表里面存儲(chǔ)的是Method 類型的。圖一中selector 就是指 Method的 SEL,  address就是指Method的 IMP。

             

            一個(gè)方法 Method,其包含一個(gè)方法選標(biāo) SEL – 表示該方法的名稱,一個(gè)types – 表示該方法參數(shù)的類型,一個(gè) IMP  - 指向該方法的具體實(shí)現(xiàn)的函數(shù)指針。

            Method 在頭文件 objc_class.h中定義如下:

            typedef struct objc_method *Method;

            typedef struct objc_ method {

                SEL method_name;

                char *method_types;

                IMP method_imp;

            };


            SEL 的含義:

            在前面我們看到方法選標(biāo) SEL 的定義為:

            typedef struct objc_selector   *SEL;   

            它是一個(gè)指向 objc_selector 指針,表示方法的名字/簽名。如下所示,打印出 selector。

            -(NSInteger)maxIn:(NSInteger)a theOther:(NSInteger)b

            {

                return (a > b) ? a : b;

            }

             

            NSLog(@"SEL=%s", @selector(maxIn:theOther:));

             

            輸出:SEL=maxIn:theOther:

             

            不同的類可以擁有相同的 selector,這個(gè)沒(méi)有問(wèn)題,因?yàn)椴煌惖膶?shí)例對(duì)象performSelector相同的 selector 時(shí),會(huì)在各自的消息選標(biāo)(selector)/實(shí)現(xiàn)地址(address) 方法鏈表中根據(jù) selector 去查找具體的方法實(shí)現(xiàn)IMP, 然后用這個(gè)方法實(shí)現(xiàn)去執(zhí)行具體的實(shí)現(xiàn)代碼。這是一個(gè)動(dòng)態(tài)綁定的過(guò)程,在編譯的時(shí)候,我們不知道最終會(huì)執(zhí)行哪一些代碼,只有在執(zhí)行的時(shí)候,通過(guò)selector去查詢,我們才能確定具體的執(zhí)行代碼。

            IMP 的含義:

            在前面我們也看到 IMP 的定義為:

            typedef id (*IMP)(id, SEL, ...);

            根據(jù)前面id 的定義,我們知道 id是一個(gè)指向 objc_object 結(jié)構(gòu)體的指針,該結(jié)構(gòu)體只有一個(gè)成員isa,所以任何繼承自 NSObject 的類對(duì)象都可以用id 來(lái)指代,因?yàn)?NSObject 的第一個(gè)成員實(shí)例就是isa。

            至此,我們就很清楚地知道 IMP  的含義:IMP 是一個(gè)函數(shù)指針,這個(gè)被指向的函數(shù)包含一個(gè)接收消息的對(duì)象id(self  指針), 調(diào)用方法的選標(biāo) SEL (方法名),以及不定個(gè)數(shù)的方法參數(shù),并返回一個(gè)id。也就是說(shuō) IMP 是消息最終調(diào)用的執(zhí)行代碼,是方法真正的實(shí)現(xiàn)代碼 。我們可以像在C語(yǔ)言里面一樣使用這個(gè)函數(shù)指針。

            NSObject 類中的methodForSelector:方法就是這樣一個(gè)獲取指向方法實(shí)現(xiàn)IMP 的指針,methodForSelector:返回的指針和賦值的變量類型必須完全一致,包括方法的參數(shù)類型和返回值類型。

            下面的例子展示了怎么使用指針來(lái)調(diào)用setFilled:的方法實(shí)現(xiàn):

            void (*setter)(id, SEL, BOOL);

            int i;

             

            setter = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];

             

            for (i = 0; i < 1000; i++)

                setter(targetList[i], @selector(setFilled:), YES);

             

             

            使用methodForSelector:來(lái)避免動(dòng)態(tài)綁定將減少大部分消息的開(kāi)銷,但是這只有在指定的消息被重復(fù)發(fā)送很多次時(shí)才有意義,例如上面的for循環(huán)。

            注意,methodForSelector:是Cocoa運(yùn)行時(shí)系統(tǒng)的提供的功能,而不是Objective-C語(yǔ)言本身的功能。

             

            消息調(diào)用過(guò)程:

            至此我們對(duì)ObjC 中的消息應(yīng)該有個(gè)大致思路了:示例

            Bird * aBird = [[Bird alloc] init];

            [aBird fly];

            中對(duì) fly 的調(diào)用,編譯器通過(guò)插入一些代碼,將之轉(zhuǎn)換為對(duì)方法具體實(shí)現(xiàn)IMP的調(diào)用,這個(gè) IMP是通過(guò)在 Bird 的類結(jié)構(gòu)中的方法鏈表中查找名稱為fly 的 選標(biāo)SEL 對(duì)應(yīng)的具體方法實(shí)現(xiàn)找到的。

            上面的思路還有一些沒(méi)有提及的話題,比如說(shuō)編譯器插入了什么代碼,如果在方法鏈表中沒(méi)有找到對(duì)應(yīng)的 IMP又會(huì)如何,這些話題在下面展開(kāi)。

             

            消息函數(shù) obj_msgSend:

            編譯器會(huì)將消息轉(zhuǎn)換為對(duì)消息函數(shù) objc_msgSend的調(diào)用,該函數(shù)有兩個(gè)主要的參數(shù):消息接收者id 和消息對(duì)應(yīng)的方法選標(biāo) SEL, 同時(shí)接收消息中的任意參數(shù):

            id objc_msgSend(id theReceiver, SELtheSelector, ...)

            如上面的消息 [aBird fly]會(huì)被轉(zhuǎn)換為如下形式的函數(shù)調(diào)用:

            objc_msgSend(aBird, @selector(fly));

             

            該消息函數(shù)做了動(dòng)態(tài)綁定所需要的一切工作:
            1,它首先找到 SEL 對(duì)應(yīng)的方法實(shí)現(xiàn) IMP。因?yàn)椴煌念悓?duì)同一方法可能會(huì)有不同的實(shí)現(xiàn),所以找到的方法實(shí)現(xiàn)依賴于消息接收者的類型。
            2, 然后將消息接收者對(duì)象(指向消息接收者對(duì)象的指針)以及方法中指定的參數(shù)傳遞給方法實(shí)現(xiàn) IMP。
            3, 最后,將方法實(shí)現(xiàn)的返回值作為該函數(shù)的返回值返回。

            編譯器會(huì)自動(dòng)插入調(diào)用該消息函數(shù)objc_msgSend的代碼,我們無(wú)須在代碼中顯示調(diào)用該消息函數(shù)。當(dāng)objc_msgSend找到方法對(duì)應(yīng)的實(shí)現(xiàn)時(shí),它將直接調(diào)用該方法實(shí)現(xiàn),并將消息中所有的參數(shù)都傳遞給方法實(shí)現(xiàn),同時(shí),它還將傳遞兩個(gè)隱藏的參數(shù):消息的接收者以及方法名稱 SEL。這些參數(shù)幫助方法實(shí)現(xiàn)獲得了消息表達(dá)式的信息。它們被認(rèn)為是”隱藏“的是因?yàn)樗鼈儾](méi)有在定義方法的源代碼中聲明,而是在代碼編譯時(shí)是插入方法的實(shí)現(xiàn)中的。

            盡管這些參數(shù)沒(méi)有被顯示聲明,但在源代碼中仍然可以引用它們(就象可以引用消息接收者對(duì)象的實(shí)例變量一樣)。在方法中可以通過(guò)self來(lái)引用消息接收者對(duì)象,通過(guò)選標(biāo)_cmd來(lái)引用方法本身。在下面的例子中,_cmd 指的是strange方法,self指的收到strange消息的對(duì)象。

            - strange

            {

                id target = getTheReceiver();

                SEL method = getTheMethod();

             

                if (target == self || mothod == _cmd)

                    return nil;

             

                return [target performSelector:method];

            }

             

            在這兩個(gè)參數(shù)中,self更有用一些。實(shí)際上,它是在方法實(shí)現(xiàn)中訪問(wèn)消息接收者對(duì)象的實(shí)例變量的途徑。

             

            查找 IMP 的過(guò)程:

            前面說(shuō)了,objc_msgSend 會(huì)根據(jù)方法選標(biāo) SEL 在類結(jié)構(gòu)的方法列表中查找方法實(shí)現(xiàn)IMP。這里頭有一些文章,我們?cè)谇懊娴念惤Y(jié)構(gòu)中也看到有一個(gè)叫objc_cache *cache 的成員,這個(gè)緩存為提高效率而存在的。每個(gè)類都有一個(gè)獨(dú)立的緩存,同時(shí)包括繼承的方法和在該類中定義的方法。。

             下面來(lái)剖析一段蘋果官方的源碼:

            static Method look_up_method(Class cls, SEL sel, BOOL withCache, BOOL withResolver)
            {
                Method meth 
            = NULL;

                
            if (withCache) {
                    meth 
            = _cache_getMethod(cls, sel, &_objc_msgForward_internal);
                    
            if (meth == (Method)1) {
                        
            // Cache contains forward:: . Stop searching.
                        return NULL;
                    }
                }

                
            if (!meth) meth = _class_getMethod(cls, sel);

                
            if (!meth  &&  withResolver) meth = _class_resolveMethod(cls, sel);

                
            return meth;
            }


            通過(guò)分析上面的代碼,可以看到,查找時(shí):

             

            1,首先去該類的方法 cache 中查找,如果找到了就返回它;

            2,如果沒(méi)有找到,就去該類的方法列表中查找。如果在該類的方法列表中找到了,則將 IMP 返回,并將它加入cache中緩存起來(lái)。根據(jù)最近使用原則,這個(gè)方法再次調(diào)用的可能性很大,緩存起來(lái)可以節(jié)省下次調(diào)用再次查找的開(kāi)銷。3,3,如果在該類的方法列表中沒(méi)找到對(duì)應(yīng)的 IMP,在通過(guò)該類結(jié)構(gòu)中的 super_class指針在其父類結(jié)構(gòu)的方法列表中去查找,直到在某個(gè)父類的方法列表中找到對(duì)應(yīng)的IMP,返回它,并加入cache中;

            4,如果在自身以及所有父類的方法列表中都沒(méi)有找到對(duì)應(yīng)的 IMP,則看是不是可以進(jìn)行動(dòng)態(tài)方法決議(后面有專文講述這個(gè)話題);

            5,如果動(dòng)態(tài)方法決議沒(méi)能解決問(wèn)題,進(jìn)入下面要講的消息轉(zhuǎn)發(fā)流程。

             

             

            便利函數(shù):

            我們可以通過(guò)NSObject的一些方法獲取運(yùn)行時(shí)信息或動(dòng)態(tài)執(zhí)行一些消息:

             class   返回對(duì)象的類;

             isKindOfClass 和 isMemberOfClass檢查對(duì)象是否在指定的類繼承體系中;

             respondsToSelector 檢查對(duì)象能否相應(yīng)指定的消息;

             conformsToProtocol 檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法;

             methodForSelector  返回指定方法實(shí)現(xiàn)的地址。

             performSelector:withObject 執(zhí)行SEL 所指代的方法。

              

            消息轉(zhuǎn)發(fā):

            通常,給一個(gè)對(duì)象發(fā)送它不能處理的消息會(huì)得到出錯(cuò)提示,然而,Objective-C運(yùn)行時(shí)系統(tǒng)在拋出錯(cuò)誤之前,會(huì)給消息接收對(duì)象發(fā)送一條特別的消息forwardInvocation 來(lái)通知該對(duì)象,該消息的唯一參數(shù)是個(gè)NSInvocation類型的對(duì)象——該對(duì)象封裝了原始的消息和消息的參數(shù)。

            我們可以實(shí)現(xiàn)forwardInvocation:方法來(lái)對(duì)不能處理的消息做一些默認(rèn)的處理,也可以將消息轉(zhuǎn)發(fā)給其他對(duì)象來(lái)處理,而不拋出錯(cuò)誤。


            關(guān)于消息轉(zhuǎn)發(fā)的作用,可以考慮如下情景:假設(shè),我們需要設(shè)計(jì)一個(gè)能夠響應(yīng)negotiate消息的對(duì)象,并且能夠包括其它類型的對(duì)象對(duì)消息的響應(yīng)。 通過(guò)在negotiate方法的實(shí)現(xiàn)中將negotiate消息轉(zhuǎn)發(fā)給其它的對(duì)象來(lái)很容易的達(dá)到這一目的。

            更進(jìn)一步,假設(shè)我們希望我們的對(duì)象和另外一個(gè)類的對(duì)象對(duì)negotiate的消息的響應(yīng)完全一致。一種可能的方式就是讓我們的類繼承其它類的方法實(shí)現(xiàn)。 然后,有時(shí)候這種方式不可行,因?yàn)槲覀兊念惡推渌惪赡苄枰诓煌睦^承體系中響應(yīng)negotiate消息。

            雖然我們的類無(wú)法繼承其它類的negotiate方法,但我們?nèi)匀豢梢蕴峁┮粋€(gè)方法實(shí)現(xiàn),這個(gè)方法實(shí)現(xiàn)只是簡(jiǎn)單的將negotiate消息轉(zhuǎn)發(fā)給其他類的對(duì)象,就好像從其它類那兒“借”來(lái)的現(xiàn)一樣。如下所示:

            - negotiate

            {

                if ([someOtherObject respondsToSelector:@selector(negotiate)])

                    return [someOtherObject negotiate];

             

                return self;

            }

            這種方式顯得有欠靈活,特別是有很多消息都希望傳遞給其它對(duì)象時(shí),我們就必須為每一種消息提供方法實(shí)現(xiàn)。此外,這種方式不能處理未知的消息。當(dāng)我們寫下代碼時(shí),所有我們需要轉(zhuǎn)發(fā)的消息的集合都必須確定。然而,實(shí)際上,這個(gè)集合會(huì)隨著運(yùn)行時(shí)事件的發(fā)生,新方法或者新類的定義而變化。

            forwardInvocation:消息給這個(gè)問(wèn)題提供了一個(gè)更特別的,動(dòng)態(tài)的解決方案:當(dāng)一個(gè)對(duì)象由于沒(méi)有相應(yīng)的方法實(shí)現(xiàn)而無(wú)法響應(yīng)某消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過(guò)forwardInvocation:消息通知該對(duì)象。每個(gè)對(duì)象都從NSObject類中繼承了forwardInvocation:方法。然而,NSObject中的方法實(shí)現(xiàn)只是簡(jiǎn)單地調(diào)用了doesNotRecognizeSelector:。通過(guò)實(shí)現(xiàn)我們自己的forwardInvocation:方法,我們可以在該方法實(shí)現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對(duì)象。

            要轉(zhuǎn)發(fā)消息給其它對(duì)象,forwardInvocation:方法所必須做的有:
            1,決定將消息轉(zhuǎn)發(fā)給誰(shuí),并且
            2,將消息和原來(lái)的參數(shù)一塊轉(zhuǎn)發(fā)出去。

            消息可以通過(guò)invokeWithTarget:方法來(lái)轉(zhuǎn)發(fā):

             

            - (void) forwardInvocation:(NSInvocation *)anInvocation

            {

                if ([someOtherObject respondsToSelector:[anInvocation selector]])

                    [anInvocation invokeWithTarget:someOtherObject];

             

                else

                    [super forwardInvocation:anInvocation];

            }

             

             

            轉(zhuǎn)發(fā)消息后的返回值將返回給原來(lái)的消息發(fā)送者。您可以將返回任何類型的返回值,包括: id,結(jié)構(gòu)體,浮點(diǎn)數(shù)等。

            forwardInvocation:方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象?;蛘咚部梢韵笠粋€(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象。它可以將一個(gè)消息翻譯成另外一個(gè)消息,或者簡(jiǎn)單的"吃掉“某些消息,因此沒(méi)有響應(yīng)也沒(méi)有錯(cuò)誤。forwardInvocation:方法也可以對(duì)不同的消息提供同樣的響應(yīng),這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力。

            注意: forwardInvocation:方法只有在消息接收對(duì)象中無(wú)法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用。 所以,如果我們希望一個(gè)對(duì)象將negotiate消息轉(zhuǎn)發(fā)給其它對(duì)象,則這個(gè)對(duì)象不能有negotiate方法,也不能在動(dòng)態(tài)方法決議中為之提供實(shí)現(xiàn)。否則,forwardInvocation:將不可能會(huì)被調(diào)用。

            參考資料:

            Objective-CRuntime Reference:

            http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html

            Objective-C Runtime Programming Guide:
            http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html


            posted on 2011-08-15 20:20 羅朝輝 閱讀(7960) 評(píng)論(1)  編輯 收藏 引用 所屬分類: 移動(dòng)開(kāi)發(fā)Cocoa 開(kāi)發(fā)

            評(píng)論

            # re: 深入淺出ObjC之消息 [未登錄](méi) 2011-08-15 22:54 chentan
            非常好的文章, 燃起了我對(duì)objc學(xué)習(xí)的興趣,謝謝  回復(fù)  更多評(píng)論
              

            无码超乳爆乳中文字幕久久| 色欲综合久久躁天天躁| 热re99久久6国产精品免费| 日韩乱码人妻无码中文字幕久久| 亚洲成色www久久网站夜月| 99久久精品国产免看国产一区| 国产精品久久久久9999| 久久亚洲av无码精品浪潮| 国产成人无码精品久久久性色| 久久久久久无码Av成人影院| 国产综合成人久久大片91| 香蕉久久夜色精品国产尤物| 久久久久久亚洲Av无码精品专口| 91精品国产91久久久久久蜜臀| 久久91精品国产91| 91久久精品国产免费直播| 狠狠色噜噜色狠狠狠综合久久| 伊人久久大香线蕉精品| 午夜不卡久久精品无码免费| 精品久久久久久无码人妻热| 久久香综合精品久久伊人| 99热都是精品久久久久久| 人妻久久久一区二区三区| 久久久久亚洲AV无码专区桃色| 久久ZYZ资源站无码中文动漫| 久久人人爽人人爽人人片AV东京热| 久久一日本道色综合久久| 婷婷久久五月天| 亚洲综合久久夜AV | 久久久久久一区国产精品| 国产精品久久久久天天影视| 性高湖久久久久久久久| 久久综合九色综合网站| 一本一道久久a久久精品综合| 99久久精品国产毛片| 久久99国产精品二区不卡| 久久精品国产亚洲AV香蕉| 精品无码久久久久久午夜| 久久久久成人精品无码中文字幕 | 无码国内精品久久人妻麻豆按摩| 69久久夜色精品国产69|