久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

深入淺出ObjC之消息

 oskycar 2014-03-23

深入淺出ObjC消息   

羅朝輝 (http://ww./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é)議鏈表*/

};

 

由此可見,,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è)沒有問(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)綁定將減少大部分消息的開銷,,但是這只有在指定的消息被重復(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)找到的,。

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

 

消息函數(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)樗鼈儾]有在定義方法的源代碼中聲明,而是在代碼編譯時(shí)是插入方法的實(shí)現(xiàn)中的,。

盡管這些參數(shù)沒有被顯示聲明,,但在源代碼中仍然可以引用它們(就象可以引用消息接收者對(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,,如果沒有找到,就去該類的方法列表中查找,。如果在該類的方法列表中找到了,,則將 IMP 返回,并將它加入cache中緩存起來(lái),。根據(jù)最近使用原則,,這個(gè)方法再次調(diào)用的可能性很大,緩存起來(lái)可以節(jié)省下次調(diào)用再次查找的開銷,。3,,3,如果在該類的方法列表中沒找到對(duì)應(yīng)的 IMP,,在通過(guò)該類結(jié)構(gòu)中的 super_class指針在其父類結(jié)構(gòu)的方法列表中去查找,,直到在某個(gè)父類的方法列表中找到對(duì)應(yīng)的IMP,返回它,,并加入cache中,;

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

5,如果動(dòng)態(tà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ì)象由于沒有相應(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)單的"吃掉“某些消息,因此沒有響應(yīng)也沒有錯(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


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多