• <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>

            羅朝輝(飄飄白云)

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

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

             [深入淺出Cocoa]之消息(二)-詳解動態(tài)方法決議(Dynamic Method Resolution)

            羅朝輝 (http://www.cnblogs.com/kesalin/)

            本文遵循“署名-非商業(yè)用途-保持一致”創(chuàng)作公用協(xié)議
             

            序言

            如果我們在 Objective C 中向一個對象發(fā)送它無法處理的消息,會出現(xiàn)什么情況呢?根據(jù)前文《深入淺出Cocoa之消息》的介紹,我們知道發(fā)送消息是通過 objc_send(id, SEL, ...) 來實現(xiàn)的,它會首先在對象的類對象的 cache,method list 以及父類對象的 cache, method list 中依次查找 SEL 對應的 IMP;如果沒有找到且實現(xiàn)了動態(tài)方法決議機制就會進行決議,如果沒有實現(xiàn)動態(tài)方法決議機制或決議失敗且實現(xiàn)了消息轉(zhuǎn)發(fā)機制就會進入消息轉(zhuǎn)發(fā)流程,否則程序 crash。也就是說如果同時提供了動態(tài)方法決議和消息轉(zhuǎn)發(fā),那么動態(tài)方法決議先于消息轉(zhuǎn)發(fā),只有當動態(tài)方法決議依然無法正確決議 selector 的實現(xiàn),才會嘗試進行消息轉(zhuǎn)發(fā)。在前文中,我并沒有詳細講解動態(tài)方法決議,因此本文將詳細介紹之。

            本文代碼下載:點此下載

            一,向一個對象發(fā)送該對象無法處理的消息

            如下代碼:

            @interface Foo : NSObject

            -(void)Bar;

            @end

            @implementation Foo

            -(void)Bar
            {
                NSLog(@" >> Bar() in Foo");
            }

            @end

            /////////////////////////////////////////////////
            #import "Foo.h"

            int main (int argc, const char * argv[])
            {

                @autoreleasepool {
                    
                    Foo * foo = [[Foo alloc] init];
                    
                    [foo Bar];
                    
                    [foo MissMethod];
                    
                    [foo release];
                }
                return 0;
            }

            在編譯時,XCode 會提示警告:

            Instance method '-MissMethod' not found (return type defaults to 'id')

            如果,我們忽視該警告運行之,一定會 crash:

            >> Bar() in Foo
            -[Foo MissMethod]: unrecognized selector sent to instance 0x10010c840
            *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Foo MissMethod]: unrecognized selector sent to instance 0x10010c840'
            *** Call stack at first throw:
            ......
            terminate called after throwing an instance of 'NSException'

            下劃線部分就是造成 crash 的原因:對象無法處理 MissMethod 對應的 selector,也就是沒有相應的實現(xiàn)。

             

            二,動態(tài)方法決議

            Objective C 提供了一種名為動態(tài)方法決議的手段,使得我們可以在運行時動態(tài)地為一個 selector 提供實現(xiàn)。我們只要實現(xiàn) +resolveInstanceMethod: 和/或 +resolveClassMethod: 方法,并在其中為指定的 selector  提供實現(xiàn)即可(通過調(diào)用運行時函數(shù) class_addMethod 來添加)。這兩個方法都是 NSObject 中的類方法,其原型為:

            + (BOOL)resolveClassMethod:(SEL)name;
            + (BOOL)resolveInstanceMethod:(SEL)name;

            參數(shù) name 是需要被動態(tài)決議的 selector;返回值文檔中說是表示動態(tài)決議成功與否。但在上面的例子中(不涉及消息轉(zhuǎn)發(fā)的情況下),如果在該函數(shù)內(nèi)為指定的 selector  提供實現(xiàn),無論返回 YES 還是 NO,編譯運行都是正確的;但如果在該函數(shù)內(nèi)并不真正為 selector 提供實現(xiàn),無論返回 YES 還是 NO,運行都會 crash,道理很簡單,selector 并沒有對應的實現(xiàn),而又沒有實現(xiàn)消息轉(zhuǎn)發(fā)。resolveInstanceMethod 是為對象方法進行決議,而 resolveClassMethod 是為類方法進行決議。

            下面我們用動態(tài)方法決議手段來修改上面的代碼:

            //
            //  Foo.m
            //  DeepIntoMethod
            //
            //  Created by 飄飄白云 on 12-11-13.
            //  Copyright (c) 2012年 kesalin@gmail.com All rights reserved.
            //

            #import "Foo.h"
            #include <objc/runtime.h>

            void dynamicMethodIMP(id self, SEL _cmd) {
                NSLog(@" >> dynamicMethodIMP");
            }

            @implementation Foo

            -(void)Bar
            {
                NSLog(@" >> Bar() in Foo");
            }

            + (BOOL)resolveInstanceMethod:(SEL)name
            {   
                NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));
                
                if (name == @selector(MissMethod)) {
                    class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
                    return YES;
                }
                
                return [super resolveInstanceMethod:name];
            }

            + (BOOL)resolveClassMethod:(SEL)name
            {
                NSLog(@" >> Class resolving %@", NSStringFromSelector(name));
                
                return [super resolveClassMethod:name];
            }

            @end

            在前文《深入淺出Cocoa之消息》中已經(jīng)介紹過 Objective C 中的方法其實就是至少帶有兩個參數(shù)(self 和 _cmd)的普通 C 函數(shù),因此在上面的代碼中提供這樣一個 C 函數(shù) dynamicMethodIMP,讓它來充當對象方法 MissMethod 這個 selector 的動態(tài)實現(xiàn)。因為 MissMethod 是被對象所調(diào)用,所以它被認為是一個對象方法,因而應該在 resolveInstanceMethod 方法中為其提供實現(xiàn)。通過調(diào)用

            class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); 

            就能在運行期動態(tài)地為 name 這個 selector 添加實現(xiàn):dynamicMethodIMP。class_addMethod 是運行時函數(shù),所以需要導入頭文件:objc/runtime.h。

            再次編譯運行前面的測試代碼,輸出如下:

              >> Bar() in Foo.
              >> Instance resolving MissMethod
              >> dynamicMethodIMP called.
              >> Instance resolving _doZombieMe

            dynamicMethodIMP 被調(diào)用了,crash 沒有了!萬事大吉!

            注意:這里兩次調(diào)用了 resolveInstanceMethod,而且兩次決議的 selector 在不同的系統(tǒng)下是不同的,上面演示的是 10.7 系統(tǒng)下第一個決議 MissMethod,第二個決議 _doZombieMe;在 10.6 系統(tǒng)下兩次都是決議 MissMethod。

            下面我把 resolveInstanceMethod 方法中為 selector 添加實現(xiàn)的那一行屏蔽了,消息轉(zhuǎn)發(fā)就應該會進行:

            //class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");

            再次編譯運行,此時輸出:

             >> Bar() in Foo.
             >> Instance resolving MissMethod
             +[Foo resolveInstanceMethod:MissMethod] returned YES, but no new implementation of -[Foo MissMethod] was found
              >> Instance resolving _doZombieMe
             objc[1223]: +[Foo resolveInstanceMethod:MissMethod] returned YES, but no new implementation of -[Foo MissMethod] was found
             -[Foo MissMethod]: unrecognized selector sent to instance 0x10010c880
              *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Foo MissMethod]: unrecognized selector sent to instance 0x10010c880'
             *** Call stack at first throw:
             ......

            在這里,resolveInstanceMethod 使詐了,它聲稱成功(返回 YES )決議了 selector,但是并沒有真正提供實現(xiàn),被編譯器發(fā)覺而提示相應的錯誤信息。那它的返回值到底有什么作用呢,在它沒有提供真正的實現(xiàn),并且提供了消息轉(zhuǎn)發(fā)機制的情況下,YES 表示不進行后續(xù)的消息轉(zhuǎn)發(fā),返回  NO 則表示要進行后續(xù)的消息轉(zhuǎn)發(fā)。

             

            三,源碼剖析

            讓我們來看看運行時系統(tǒng)是如何進行動態(tài)方法決議的,下面的代碼來自蘋果官方公開的源碼 objc-class.mm,我在其中添加了中文注釋:

            1,首先是判斷是不是要進行類方法決議,如果不是或決議失敗,則進行實例方法決議(請參考:《深入淺出Cocoa之類與對象》):

            /***********************************************************************
            * _class_resolveMethod
            * Call +resolveClassMethod or +resolveInstanceMethod and return 
            * the method added or NULL. 
            * Assumes the method doesn't exist already.
            *********************************************************************
            */
            __private_extern__ Method _class_resolveMethod(Class cls, SEL sel)
            {
                Method meth = NULL;

                if (_class_isMetaClass(cls)) {
                    meth = _class_resolveClassMethod(cls, sel);
                }
                if (!meth) {
                    meth = _class_resolveInstanceMethod(cls, sel);
                }

                if (PrintResolving  &&  meth) {
                    _objc_inform("RESOLVE: method %c[%s %s] dynamically resolved to %p", 
                                 class_isMetaClass(cls) ? '+' : '-', 
                                 class_getName(cls), sel_getName(sel), 
                                 method_getImplementation(meth));
                }
                
                return meth;
            }

            2,類方法決議與實例方法決議大體相似,在這里就只看實例方法決議部分了:

            /***********************************************************************
             * _class_resolveInstanceMethod
             * Call +resolveInstanceMethod and return the method added or NULL.
             * cls should be a non-meta class.
             * Assumes the method doesn't exist already.
             *********************************************************************
            */
            static Method _class_resolveInstanceMethod(Class cls, SEL sel)
            {
                BOOL resolved;
                Method meth = NULL;
                
                // 是否實現(xiàn)了 resolveInstanceMethod,如果沒有返回 NULL
                if (!look_up_method(((id)cls)->isa, SEL_resolveInstanceMethod, 
                                    YES /*cache*/, NO /*resolver*/))
                {
                    return NULL;
                }
                
                // 調(diào)用 resolveInstanceMethod,并獲取返回值
                resolved = ((BOOL(*)(id, SEL, SEL))objc_msgSend)((id)cls, SEL_resolveInstanceMethod, sel);
                
                if (resolved) {
                    // 返回值為 YES,表示 resolveInstanceMethod 聲稱它已經(jīng)成功添加實現(xiàn),則再次查找 method list 
                    
            // +resolveClassMethod adds to self
                    meth = look_up_method(cls, sel, YES/*cache*/, NO/*resolver*/);
                    
                    if (!meth) {
                        // resolveInstanceMethod 使詐了,它聲稱成功添加實現(xiàn)了,但實際沒有,給出警告信息,并返回 NULL
                        
            // Method resolver didn't add anything?
                        _objc_inform("+[%s resolveInstanceMethod:%s] returned YES, but "
                                     "no new implementation of %c[%s %s] was found", 
                                     class_getName(cls),
                                     sel_getName(sel), 
                                     class_isMetaClass(cls) ? '+' : '-', 
                                     class_getName(cls), 
                                     sel_getName(sel));
                        return NULL;
                    }
                }
                
                // 其他情況下返回 NULL
                return meth;
            }

            這段代碼很容易理解:

            1,首先判斷是否實現(xiàn)了 resolveInstanceMethod,如果沒有實現(xiàn),返回 NULL,進入下一步處理;

            2,如果實現(xiàn)了,調(diào)用 resolveInstanceMethod,獲取返回值;

            3,如果返回值為 YES,表示 resolveInstanceMethod 聲稱它已經(jīng)提供了 selector 的實現(xiàn),因此再次查找 method list,如果依然找到對應的 IMP,則返回該實現(xiàn),否則提示警告信息,返回 NULL,進入下一步處理;

            4,如果返回值為 NO,返回 NULL,進入下一步處理;

             

            四,加入消息轉(zhuǎn)發(fā)

            在前文《深入淺出Cocoa之消息》一文中,我演示了一個消息轉(zhuǎn)發(fā)的示例,下面我把動態(tài)方法決議部分去除,把消息轉(zhuǎn)發(fā)部分添加進來:

            // Proxy
            @interface Proxy : NSObject

            -(void)MissMethod;

            @end

            @implementation Proxy

            -(void)MissMethod
            {
                NSLog(@" >> MissMethod() called in Proxy.");
            }

            @end

            // Foo
            @interface Foo : NSObject

            -(void)Bar;

            @end

            @implementation Foo

            - (void)forwardInvocation:(NSInvocation *)anInvocation
            {
                SEL name = [anInvocation selector];
                NSLog(@" >> forwardInvocation for selector %@", NSStringFromSelector(name));
                
                Proxy * proxy = [[[Proxy alloc] init] autorelease];
                if ([proxy respondsToSelector:name]) {
                    [anInvocation invokeWithTarget:proxy];
                }
                else {
                    [super forwardInvocation:anInvocation];
                }
            }

            - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
                return [Proxy instanceMethodSignatureForSelector:aSelector];
            }

            -(void)Bar
            {
                NSLog(@" >> Bar() in Foo.");
            }

            @end

            運行測試代碼,輸出如下:

              >> Bar() in Foo.
              >> forwardInvocation for selector MissMethod
              >> MissMethod() called in Proxy.

            如果我把動態(tài)方法決議部分代碼也加入進來輸出又是怎樣呢?下面只列出了 Foo 的實現(xiàn)代碼,其他代碼不變動。

            @implementation Foo

            +(BOOL)resolveInstanceMethod:(SEL)name
            {
                NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));
                
                if (name == @selector(MissMethod)) {
                    class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
                    return YES;
                }
                
                return [super resolveInstanceMethod:name];
            }

            +(BOOL)resolveClassMethod:(SEL)name
            {
                NSLog(@" >> Class resolving %@", NSStringFromSelector(name));
                return [super resolveClassMethod:name];
            }

            - (void)forwardInvocation:(NSInvocation *)anInvocation
            {
                SEL name = [anInvocation selector];
                NSLog(@" >> forwardInvocation for selector %@", NSStringFromSelector(name));
                
                Proxy * proxy = [[[Proxy alloc] init] autorelease];
                if ([proxy respondsToSelector:name]) {
                    [anInvocation invokeWithTarget:proxy];
                }
                else {
                    [super forwardInvocation:anInvocation];
                }
            }

            - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
                return [Proxy instanceMethodSignatureForSelector:aSelector];
            }

            -(void)Bar
            {
                NSLog(@" >> Bar() in Foo.");
            }

            @end

            此時,輸出為:

              >> Bar() in Foo.
              >> Instance resolving MissMethod
              >> dynamicMethodIMP called.
              >> Instance resolving _doZombieMe

            注意到了沒,消息轉(zhuǎn)發(fā)沒有進行!在前文中說過,消息轉(zhuǎn)發(fā)只有在對象無法正常處理消息時才會調(diào)用,而在這里我在動態(tài)方法決議中為 selector 提供了實現(xiàn),使得對象可以處理該消息,所以消息轉(zhuǎn)發(fā)不會繼續(xù)了。官方文檔中說:

            If you implement resolveInstanceMethod: but want particular selectors to actually be forwarded via the forwarding mechanism, you return NO for those selectors.

            文檔里的說法其實并不準確,只有在 resolveInstanceMethod 的實現(xiàn)中沒有真正為 selector 提供實現(xiàn),并返回 NO 的情況下才會進入消息轉(zhuǎn)發(fā)流程;否則絕不會進入消息轉(zhuǎn)發(fā)流程,程序要么調(diào)用正確的動態(tài)方法,要么 crash。這也與前面的源碼不太一致,我猜測在比上面源碼的更高層次的地方,再次查找了 method list,如果提供了實現(xiàn)就能夠找到該實現(xiàn)。

            下面我把 resolveInstanceMethod 方法中為 selector 添加實現(xiàn)的那一行屏蔽了,消息轉(zhuǎn)發(fā)就應該會進行:

            //class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");

            再次編譯運行,此時輸出正如前面所推斷的那樣:

              >> Bar() in Foo.
              >> Instance resolving MissMethod
              objc[1618]: +[Foo resolveInstanceMethod:MissMethod] returned YES, but no new implementation of -[Foo MissMethod] was found
              >> forwardInvocation for selector MissMethod
              >> MissMethod() called in Proxy.
              >> Instance resolving _doZombieMe

            進行了消息轉(zhuǎn)發(fā)!而且編譯器很善意地提示(見前面源碼剖析):哎呀,你不能欺騙我嘛,你說添加了實現(xiàn)(返回YES),其實還是沒有呀!然后編譯器就無奈地去看能不能消息轉(zhuǎn)發(fā)了。當然如果把返回值修改為 NO 就不會有該警告出現(xiàn),其他的輸出不變。

             

            五,總結

            從上面的示例演示可以看出,動態(tài)方法決議是先于消息轉(zhuǎn)發(fā)的。

            如果向一個 Objective C 對象對象發(fā)送它無法處理的消息(selector),那么編譯器會按照如下次序進行處理:

            1,首先看是否為該 selector 提供了動態(tài)方法決議機制,如果提供了則轉(zhuǎn)到 2;如果沒有提供則轉(zhuǎn)到 3;

            2,如果動態(tài)方法決議真正為該 selector 提供了實現(xiàn),那么就調(diào)用該實現(xiàn),完成消息發(fā)送流程,消息轉(zhuǎn)發(fā)就不會進行了;如果沒有提供,則轉(zhuǎn)到 3;

            3,其次看是否為該 selector 提供了消息轉(zhuǎn)發(fā)機制,如果提供了消息了則進行消息轉(zhuǎn)發(fā),此時,無論消息轉(zhuǎn)發(fā)是怎樣實現(xiàn)的,程序均不會 crash。(因為消息調(diào)用的控制權完全交給消息轉(zhuǎn)發(fā)機制處理,即使消息轉(zhuǎn)發(fā)并沒有做任何事情,運行也不會有錯誤,編譯器更不會有錯誤提示。);如果沒提供消息轉(zhuǎn)發(fā)機制,則轉(zhuǎn)到 4;

            4,運行報錯:無法識別的 selector,程序 crash;

             

            六,引用

            官方運行時源代碼:http://www.opensource.apple.com/source/objc4/objc4-532/runtime/

            Objective-C Runtime Programming Guide

            深入淺出Cocoa之消息

            深入淺出Cocoa之類與對象

            posted on 2012-11-14 23:53 羅朝輝 閱讀(2353) 評論(0)  編輯 收藏 引用 所屬分類: Cocoa 開發(fā)
            中文字幕精品无码久久久久久3D日动漫| 色婷婷综合久久久中文字幕| 久久综合香蕉国产蜜臀AV| 亚洲v国产v天堂a无码久久| 久久精品无码专区免费| 国产精品99久久不卡| 日本道色综合久久影院| 久久综合久久久| 一本久久久久久久| 久久97久久97精品免视看秋霞| 久久99精品综合国产首页| 久久国产乱子精品免费女| 99久久精品久久久久久清纯| 亚洲精品高清国产一久久| 成人国内精品久久久久影院VR| 99久久精品久久久久久清纯| A级毛片无码久久精品免费| 久久se精品一区二区影院 | 久久久久无码精品国产| 久久久噜噜噜久久中文福利| 久久精品国产精品国产精品污| 久久久久久久99精品免费观看| 国产精品成人99久久久久| 久久免费视频6| A级毛片无码久久精品免费| 国产精品久久精品| 精品99久久aaa一级毛片| 久久久久久午夜精品| …久久精品99久久香蕉国产| 一本久久a久久精品综合夜夜| 亚洲欧美久久久久9999| 久久99精品久久只有精品| 一本大道加勒比久久综合| 久久久久久伊人高潮影院| 国产精品99久久精品| 久久国产精品免费一区| 久久午夜伦鲁片免费无码| 久久精品女人天堂AV麻| 久久久久免费看成人影片| 99久久无码一区人妻| 亚洲乱码中文字幕久久孕妇黑人|