Objective-C運(yùn)行時(shí)編程指南

前言

Objective-C這門語(yǔ)言將盡可能多的決定從編譯和鏈接階段延遲到運(yùn)行階段決定。如果有可能堪嫂,它就動(dòng)態(tài)的處理一些事情奏属。這意味著這門語(yǔ)言不止需要編譯系統(tǒng)仿村,也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行編譯的代碼。運(yùn)行時(shí)系統(tǒng)就像Objective-C這門語(yǔ)言的一種操作系統(tǒng)或舞;它是這門語(yǔ)言運(yùn)行的決定性因素荆姆。


一:運(yùn)行時(shí)的版本和平臺(tái)

在不同的平臺(tái)上有不同的運(yùn)行時(shí)版本

1.遺留版本和現(xiàn)代版本

Objective-C的運(yùn)行時(shí)有兩個(gè)版本:legacymodern

  • legacy:參考Objective-C 1 Runtime Reference。
  • modern:Objective-C 2.0引入的嚷那,包含一些寫(xiě)的特性胞枕,參考Objective-C Runtime Reference。支持聲明變量的synthesis

2.平臺(tái)

iPhone應(yīng)用和OSX10.5及以后版本的64位應(yīng)用使用modern版本
其它程序(OSX平臺(tái)的32位程序)使用legacy版本


二:與運(yùn)行時(shí)的交互

Objective-C程序在以下三個(gè)層次上與運(yùn)行時(shí)進(jìn)行交互:

1.Objective-C源碼

大多數(shù)情況下魏宽,運(yùn)行時(shí)系統(tǒng)是在幕后自動(dòng)運(yùn)行的腐泻。當(dāng)你編寫(xiě)和運(yùn)行Objective-C源碼的時(shí)候其實(shí)你就已經(jīng)在使用它了。
當(dāng)編譯包含Objective-C類和方法的代碼時(shí)队询,編譯器就創(chuàng)建了實(shí)現(xiàn)這門語(yǔ)言動(dòng)態(tài)特性的數(shù)據(jù)結(jié)構(gòu)和函數(shù)調(diào)用派桩。數(shù)據(jù)結(jié)構(gòu)會(huì)捕獲類和分類定義及協(xié)議聲明中的信息;這些信息包括類和協(xié)議對(duì)象蚌斩、方法選擇子铆惑、實(shí)例變量模板和一些其它從源碼中提取的信息。最重要的運(yùn)行時(shí)函數(shù)是消息發(fā)送函數(shù)送膳,這會(huì)在下文介紹员魏。這個(gè)函數(shù)會(huì)被源碼的消息表達(dá)式喚起。

2.NSObject定義的方法

Cocoa中的大部分對(duì)象是NSObject的子類叠聋,所以大部分對(duì)象都繼承了NSObject定義的方法(NSProxy是個(gè)例外)撕阎。它的方法因此建立了適用于每個(gè)實(shí)例對(duì)象和類對(duì)象的行為。但是在少數(shù)情況下碌补,NSObject類僅僅是定義了一個(gè)事情如何做的模板虏束;它本身并不提供所有的必要代碼。
例如厦章,NSObject類定義了一個(gè)description實(shí)例方法來(lái)返回一個(gè)字符串來(lái)描述這個(gè)類的內(nèi)容镇匀。它主要用于debugging。NSObject的這個(gè)方法的實(shí)現(xiàn)并不知道這個(gè)類包含的內(nèi)容袜啃,所以它只返回類名和類的地址汗侵。NSObject的子類可以重寫(xiě)這個(gè)方法來(lái)返回更多詳細(xì)的信息。例如,NSArray返回一組它包含對(duì)象的描述晃择。
一些NSObject方法僅僅是向運(yùn)行時(shí)系統(tǒng)詢問(wèn)一些信息冀值。這些方法允許對(duì)象執(zhí)行自省(introspection)。例如:class會(huì)請(qǐng)求一個(gè)對(duì)象來(lái)鑒別它的類宫屠;isKindOfClass:isMemberOfClass:檢測(cè)一個(gè)對(duì)象在繼承體系中的位置列疗;respondsToSelector:指明一個(gè)對(duì)象是否可以接收一條指定消息;conformsToProtocol:指明一個(gè)對(duì)象是否遵循了指定的協(xié)議浪蹂;methodForSelector:提供了一個(gè)方法實(shí)現(xiàn)的地址抵栈。像以上的這些方法讓一個(gè)對(duì)象有能力進(jìn)行自省。

3.對(duì)運(yùn)行時(shí)函數(shù)的直接調(diào)用

運(yùn)行時(shí)系統(tǒng)是一個(gè)動(dòng)態(tài)的共享庫(kù)坤次,它包含由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成的公共接口古劲,見(jiàn)圖1。許多函數(shù)允許你使用簡(jiǎn)單的C語(yǔ)言復(fù)制編譯器在你書(shū)寫(xiě)Objective-C代碼所做的事情缰猴。另外一些則構(gòu)成了NSObject這個(gè)類的功能的基礎(chǔ)产艾。這些函數(shù)使得開(kāi)發(fā)一些其它的運(yùn)行時(shí)接口變得可能,并能生產(chǎn)一些能增強(qiáng)開(kāi)發(fā)環(huán)境的工具滑绒。

圖1:運(yùn)行時(shí)的頭文件


三:消息傳遞(Messaging)

消息表達(dá)式是如何轉(zhuǎn)換成objc_msgSend函數(shù)的闷堡?如何通過(guò)名字指向方法?如何利用objc_msgSend疑故?如何跳過(guò)動(dòng)態(tài)綁定杠览?請(qǐng)看下文。

1.objc_msgSend函數(shù)

在Objective-C中纵势,消息是直到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)的踱阿。編譯器將一個(gè)如下形式的消息表達(dá)式

[receiver message]

轉(zhuǎn)換成一個(gè)對(duì)消息函數(shù)objc_msgSend的調(diào)用。這個(gè)消息函數(shù)將消息的接收者和消息的方法名(也就是函數(shù)的選擇子:selector)作為它的兩個(gè)主要參數(shù):

objc_msgSend(receiver, selector)

消息中的任何參數(shù)同樣也會(huì)傳遞給objc_msgSend函數(shù):

objc_msgSend(receiver, selector, arg1, arg2, ...)

消息函數(shù)會(huì)為動(dòng)態(tài)綁定做所有必要的事情:

  • 首先钦铁,消息函數(shù)尋找選擇子所指向的procedure(方法實(shí)現(xiàn):method implementation)软舌。因?yàn)橥瑯拥姆椒赡軙?huì)被不同的類實(shí)現(xiàn),所以消息函數(shù)尋找的精確的procedure取決于消息接收者這個(gè)類牛曹。
  • 然后佛点,消息函數(shù)調(diào)用這個(gè)procedure,傳入接收對(duì)象(一個(gè)指向接收對(duì)象數(shù)據(jù)的指針)和方法指定的任何參數(shù)躏仇。
  • 最后消息函數(shù)將這個(gè)procedure的返回值作為自己的返回值傳入恋脚。

注意:編譯器會(huì)生成消息函數(shù)的調(diào)用腺办,作為開(kāi)發(fā)者永遠(yuǎn)不要直接調(diào)用這個(gè)函數(shù)焰手。

消息傳遞的關(guān)鍵取決于編譯器為每個(gè)類和對(duì)象所構(gòu)建的結(jié)構(gòu)體。每個(gè)類的結(jié)構(gòu)體包括以下兩個(gè)必要元素:

  • 一個(gè)指向父類的指針
  • 一個(gè)類派發(fā)表怀喉。這個(gè)表?yè)碛幸恍l目书妻,這些條目將方法選擇子和他們識(shí)別的指定類的方法的地址綁定起來(lái)。例如:setOrigin::方法的選擇子和setOrigin::的地址綁定起來(lái),display方法的選擇子和display的地址綁定起來(lái)躲履。

當(dāng)一個(gè)新的對(duì)象被創(chuàng)建的時(shí)候见间,它的內(nèi)存空間隨之被開(kāi)辟,它的實(shí)例變量也被初始化工猜。在這些變量中米诉,第一個(gè)是一個(gè)指向它的類結(jié)構(gòu)的指針。這個(gè)指針篷帅,稱作isa史侣,這個(gè)isa指針使這個(gè)對(duì)象可以訪問(wèn)它的類,通過(guò)這個(gè)類魏身,進(jìn)而訪問(wèn)它繼承的所有類惊橱。

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

注意:盡管嚴(yán)格來(lái)說(shuō)isa指針不是語(yǔ)言的一部分,但是一個(gè)對(duì)象想與Objective-C的運(yùn)行時(shí)系統(tǒng)協(xié)作箭昵,它卻是必須的税朴。但是你幾乎不需要?jiǎng)?chuàng)建你自己的根類,繼承自NSObjectNSProxy的對(duì)象自動(dòng)擁有這個(gè)isa指針家制。
類和對(duì)象的元素如圖2所示正林。

圖2: Messaging Framework

當(dāng)一條消息被發(fā)送到一個(gè)對(duì)象后,消息函數(shù)沿著這個(gè)對(duì)象的isa指針到達(dá)這個(gè)對(duì)象所屬類的結(jié)構(gòu)體慰丛,在結(jié)構(gòu)體的派發(fā)表中消息函數(shù)查找這個(gè)方法選擇子卓囚。如果查閱不到,objc_msgSend就會(huì)沿著isa指針在父類的派發(fā)表中查找這個(gè)選擇子诅病。持續(xù)的失敗會(huì)導(dǎo)致objc_msgSend沿著繼承體系持續(xù)查找父類的派發(fā)表直到NSObject根類哪亿。一旦消息函數(shù)定位到了這個(gè)選擇子,它就會(huì)調(diào)用派發(fā)表中的方法并傳入接收對(duì)象的數(shù)據(jù)結(jié)構(gòu)贤笆。
以上就是方法實(shí)現(xiàn)在運(yùn)行時(shí)被選擇的方式蝇棉。以面向?qū)ο缶幊痰男g(shù)語(yǔ)來(lái)說(shuō)就是:方法被動(dòng)態(tài)地綁定到消息。
為了加快消息傳遞進(jìn)程芥永,運(yùn)行時(shí)系統(tǒng)在使用選擇子和函數(shù)的地址的時(shí)候會(huì)緩存它們篡殷。每個(gè)類有一個(gè)單獨(dú)的緩存,這個(gè)緩存可以包含繼承方法的選擇子和這個(gè)類本身定義的方法的選擇子埋涧。在搜索派發(fā)表之前板辽,消息函數(shù)按慣例會(huì)首先檢查接收對(duì)象的類的緩存(假設(shè)一個(gè)方法被使用后可能會(huì)被再次使用)。如果方法選擇子在緩存中棘催,那么消息傳遞只是稍微比一個(gè)函數(shù)調(diào)用慢些(C語(yǔ)言的稱作函數(shù)調(diào)用(function)劲弦,OC稱作方法調(diào)用(method),這句話的意思應(yīng)該就是僅僅比C語(yǔ)言慢些醇坝,但是比OC快很多)邑跪。一旦一個(gè)程序運(yùn)行的時(shí)間足夠長(zhǎng)到"熱身"它的緩存,那么幾乎所有它發(fā)送的消息都可以找到一個(gè)緩存方法。程序運(yùn)行時(shí)緩存是動(dòng)態(tài)增長(zhǎng)的以便容納新的消息画畅。

2.使用隱藏的參數(shù)

當(dāng)objc_msgSend找到了這個(gè)實(shí)現(xiàn)了一個(gè)方法的procedure砸琅,它調(diào)用這個(gè)procedure并傳入消息中的所有參數(shù)。它同樣也傳入了兩個(gè)隱藏的參數(shù):

  • 接收對(duì)象
  • 方法選擇子

這些參數(shù)給每個(gè)方法實(shí)現(xiàn)關(guān)于喚起這個(gè)方法實(shí)現(xiàn)的消息表達(dá)式的明確的信息轴踱。之所以說(shuō)它們是“隱藏”的症脂,是因?yàn)樗鼈儾](méi)有在定義這個(gè)方法的源碼中聲明。在代碼編譯后它們會(huì)被插入到實(shí)現(xiàn)中淫僻。
盡管這些參數(shù)沒(méi)有明確的聲明摊腋,源碼仍然可以指向它們(就像它可以指向接收對(duì)象的實(shí)例變量一樣)。一個(gè)方法將這個(gè)消息的接收對(duì)象稱作self嘁傀,將自己的選擇子稱作_cmd兴蒸。下面的例子中,_cmd指向strange方法的選擇子细办,self指向接收strange消息的對(duì)象橙凳。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self strange];
}

- (id)strange{
    id target = getTheReceiver();
    SEL method = getTheMethod();
    if ( target == self || method == _cmd ){
        return nil;
    }
    return [target performSelector:method];
}

self是兩個(gè)參數(shù)中比較有用的一個(gè)。

3.獲取一個(gè)方法的地址

跳過(guò)動(dòng)態(tài)綁定的唯一方法就是獲取一個(gè)方法的地址然后直接調(diào)用它就像它是一個(gè)函數(shù)一樣笑撞。這種技術(shù)適用于極其罕見(jiàn)的情況:當(dāng)一個(gè)特殊的函數(shù)將會(huì)被多次成功執(zhí)行岛啸,而你想要避免方法每次執(zhí)行時(shí)消息傳遞的開(kāi)銷。
使用NSObjectmethodForSelector:方法茴肥,你可以請(qǐng)求一個(gè)指向?qū)崿F(xiàn)了一個(gè)方法的procedure坚踩,然后使用這個(gè)指針來(lái)調(diào)用proceduremethodForSelector:返回的指針必須被小心的轉(zhuǎn)換為合適的函數(shù)類型瓤狐。返回值和參數(shù)類型都應(yīng)該被包含在轉(zhuǎn)換中瞬铸。
下面的例子展示了實(shí)現(xiàn)了setFilled:方法的procedure是如何被調(diào)用的:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self circumvent];
}

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

- (void)circumvent{
    setter = (void (*)(id, SEL, BOOL))[self methodForSelector:@selector(setFilled:)];
    for (int i = 0 ; i < 1000 ; i++ ){
        setter(self, @selector(setFilled:), YES);
    }
}

- (void)setFilled:(BOOL)filled{
    NSLog(@"%@",NSStringFromSelector(_cmd));
}

傳入procedure的前兩個(gè)參數(shù)是接收對(duì)象(self)和方法選擇子(_cmd)。在語(yǔ)法上這些參數(shù)是隱藏的但是當(dāng)方法被當(dāng)做函數(shù)調(diào)用時(shí)必須明確顯示呈現(xiàn)础锐。
使用methodForSelector:來(lái)跳過(guò)動(dòng)態(tài)綁定節(jié)省了消息傳遞的大部分時(shí)間嗓节。但是這種節(jié)省只在一個(gè)特殊的消息被重復(fù)請(qǐng)求多次的情況下才會(huì)有顯著的效果,例如在for循環(huán)中皆警。
注意拦宣,methodForSelector:是由Cocoa的運(yùn)行時(shí)系統(tǒng)提供的,它不是Objective-C語(yǔ)言本身的一種特性信姓。


四:動(dòng)態(tài)方法解析

如何動(dòng)態(tài)的提供一個(gè)方法的實(shí)現(xiàn)呢鸵隧?請(qǐng)看下文

1.動(dòng)態(tài)方法解析

有些情況下你也許想動(dòng)態(tài)的提供一個(gè)方法的一個(gè)實(shí)現(xiàn)。例如意推,Objective-C聲明的屬性中包括@dynamic指令:

@dynamic propertyName;

這個(gè)指令會(huì)告訴編譯器和這個(gè)屬性相關(guān)的方法將會(huì)被動(dòng)態(tài)提供豆瘫。
可以通過(guò)實(shí)現(xiàn)resolveInstanceMethod:resolveClassMethod:方法來(lái)動(dòng)態(tài)的分別為某個(gè)實(shí)例或者類的某個(gè)指定選擇子提供一個(gè)實(shí)現(xiàn)。
一個(gè)Objective-C方法就是一個(gè)簡(jiǎn)單的包含至少兩個(gè)參數(shù)(self_cmd)的C函數(shù)左痢∶蚁郏可以使用class_addMethod函數(shù)為一個(gè)類添加一個(gè)函數(shù)作為一個(gè)方法。因此俊性,考慮以下函數(shù):

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

你可以使用resolveInstanceMethod:動(dòng)態(tài)地將這個(gè)函數(shù)添加到一個(gè)類中作為一個(gè)方法(稱作resolveThisMethodDynamically)略步,像下面這樣:

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

轉(zhuǎn)發(fā)方法(下面會(huì)在消息轉(zhuǎn)發(fā)中介紹)和動(dòng)態(tài)方法解析很大程度上是正交的。一個(gè)類有機(jī)會(huì)在觸發(fā)轉(zhuǎn)發(fā)機(jī)制前動(dòng)態(tài)解析一個(gè)方法定页。如果respondsToSelector:或者instancesRespondToSelector:被喚起趟薄,動(dòng)態(tài)方法解析有機(jī)會(huì)首先為這個(gè)選擇子提供一個(gè)IMP。如果你實(shí)現(xiàn)了resolveInstanceMethod:方法典徊,但是卻想通過(guò)轉(zhuǎn)發(fā)機(jī)制將某些選擇子進(jìn)行轉(zhuǎn)發(fā)杭煎,那么需要為這些選擇子返回NO

2.動(dòng)態(tài)加載

一個(gè)Objective-C程序在運(yùn)行時(shí)可以加載和鏈接新的類和分類(category)卒落。這些新的代碼被編入到程序中羡铲,它們被當(dāng)做初次加載的類和分類一樣。
動(dòng)態(tài)加載可以用來(lái)做許多不同的事情儡毕。例如也切,系統(tǒng)偏好設(shè)置應(yīng)用中的各種各樣的模塊就是動(dòng)態(tài)加載的。
在Cocoa環(huán)境下腰湾,動(dòng)態(tài)加載通常用來(lái)讓?xiě)?yīng)用可以自定義雷恃。其他人可以編寫(xiě)一些你程序在運(yùn)行時(shí)可以加載的模塊--就像界面生成器(Interface Builder)加載自定義的調(diào)色板、OS X系統(tǒng)偏好應(yīng)用加載自定義的偏好設(shè)置费坊。這種可加載的模塊擴(kuò)展了應(yīng)用可以做的事情倒槐。你提供了框架,但是其他人提供了代碼附井。
盡管有一個(gè)執(zhí)行Objective-C模塊動(dòng)態(tài)加載的運(yùn)行時(shí)函數(shù)(objc_loadModules闻鉴,位于objc/objc-load.h),Cocoa的NSBundle類提供了一個(gè)更加便利的接口用來(lái)動(dòng)態(tài)加載--這個(gè)類是面向?qū)ο蟮南才樱⒓闪讼嚓P(guān)的服務(wù)案狠。詳見(jiàn)NSBundle


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

向一個(gè)對(duì)象發(fā)送一個(gè)這個(gè)對(duì)象無(wú)法處理的消息會(huì)導(dǎo)致crash卷雕。但是在宣布錯(cuò)誤前节猿,運(yùn)行時(shí)系統(tǒng)給接收者第二次機(jī)會(huì)來(lái)處理這條消息。

1.轉(zhuǎn)發(fā)

如果你向一個(gè)對(duì)象發(fā)送一個(gè)這個(gè)對(duì)象無(wú)法處理的消息漫雕,在宣布錯(cuò)誤前滨嘱,運(yùn)行時(shí)系統(tǒng)向這個(gè)對(duì)象發(fā)送一條包含一個(gè)唯一參數(shù)(NSInvocation對(duì)象)的forwardInvocation:消息--NSInvocation對(duì)象封裝了最初的消息和傳遞給它的參數(shù)。
可以實(shí)現(xiàn)forwardInvocation:來(lái)給消息一個(gè)默認(rèn)的響應(yīng)浸间,或者以其它方式避免這個(gè)無(wú)法識(shí)別消息導(dǎo)致的錯(cuò)誤太雨。就像這個(gè)函數(shù)名字所指示的那樣,forwardInvocation:通常用來(lái)將這條消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象魁蒜。
為了了解轉(zhuǎn)發(fā)的范圍和目的囊扳,想象以下場(chǎng)景:首先假設(shè)你在設(shè)計(jì)一個(gè)可以響應(yīng)negotiate這條消息的對(duì)象吩翻,你期望它的響應(yīng)包含另一個(gè)對(duì)象的響應(yīng)。想要完成這個(gè)操作很簡(jiǎn)單锥咸,你只需要在你設(shè)計(jì)的對(duì)象的negotiate方法實(shí)現(xiàn)里向另一個(gè)對(duì)象傳入一條negotiate消息即可狭瞎。
再進(jìn)一步,假設(shè)你期望你設(shè)計(jì)的對(duì)象對(duì)negotiate消息的響應(yīng)就是另一個(gè)對(duì)象實(shí)現(xiàn)的響應(yīng)搏予。一種解決方式是讓你設(shè)計(jì)的類繼承另一個(gè)類的negotiate方法熊锭。但是這樣安排可能是行不通的。有很多理由導(dǎo)致你的類和實(shí)現(xiàn)negotiate的類在不同的繼承體系分支雪侥。
即使你的類不能繼承negotiate方法碗殷,你也可以“借來(lái)”這個(gè)方法:實(shí)現(xiàn)這個(gè)方法并在這個(gè)方法中向另一個(gè)類的實(shí)例發(fā)送一條negotiate消息。

- (id)negotiate
{
    if ( [someOtherObject respondsTo:@selector(negotiate)] )
        return [someOtherObject negotiate];
    return self;
}

這種處理事情的方式略顯笨重速缨,尤其是當(dāng)有許多消息你想通過(guò)你的對(duì)象傳到另外一個(gè)對(duì)象锌妻。你不得不實(shí)現(xiàn)一個(gè)方法來(lái)覆蓋每個(gè)你想從其它類"借來(lái)"的方法。此外旬牲,這種方法不可能處理這樣一種情況--當(dāng)你編寫(xiě)這種方法的代碼時(shí)从祝,你并不知道整個(gè)你想要轉(zhuǎn)發(fā)的消息集合。這個(gè)集合也許取決于運(yùn)行時(shí)的一些事件引谜,當(dāng)一些新的方法和類將來(lái)實(shí)現(xiàn)的時(shí)候也可能改變這個(gè)集合牍陌。
對(duì)于這個(gè)問(wèn)題,forwardInvocation:消息提供的第二個(gè)機(jī)會(huì)提供了一個(gè)需要更少臨時(shí)性修復(fù)(less ad hoc)的解決方式员咽,這種解決方式是動(dòng)態(tài)的而不是靜態(tài)的毒涧。它的工作原理是這樣的:當(dāng)一個(gè)對(duì)象因?yàn)闆](méi)有方法匹配消息的選擇子而無(wú)法響應(yīng)一條消息時(shí),運(yùn)行時(shí)系統(tǒng)通過(guò)向這個(gè)對(duì)象發(fā)送一條forwardInvocation:消息來(lái)通知這個(gè)對(duì)象贝室。每一個(gè)繼承自NSObject的類都繼承了forwardInvocation:方法契讲。但是這個(gè)方法的NSObject版本只是喚起了doesNotRecognizeSelector:方法。通過(guò)重寫(xiě)NSObjectforwardInvocation:方法滑频,你可以充分利用forwardInvocation:消息提供的機(jī)會(huì)來(lái)將這條消息轉(zhuǎn)發(fā)給其它的對(duì)象捡偏。
為了轉(zhuǎn)發(fā)一條消息,一個(gè)forwardInvocation:方法需要:

  • 決定消息需要發(fā)送到哪里
  • 將這條消息帶著原始參數(shù)發(fā)到那里

這條消息可以通過(guò)invokeWithTarget:方法發(fā)送:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

這條被轉(zhuǎn)發(fā)的消息的返回值返回給了最初的發(fā)送者峡迷。返回值的所有類型都可以傳遞給發(fā)送者银伟,包括id,結(jié)構(gòu)體绘搞,雙精度浮點(diǎn)型彤避。
一個(gè)forwardInvocation:方法可以扮演一個(gè)分布中心的角色,它將無(wú)法識(shí)別的消息打包發(fā)送給不同的接收者夯辖×鹪ぃ或者作為一個(gè)傳送站,將所有消息發(fā)送到相同的終點(diǎn)蒿褂。它可以轉(zhuǎn)化一條消息成另一條消息圆米,或者干脆"咽下"一些消息這樣就沒(méi)有回應(yīng)和錯(cuò)誤卒暂。一個(gè)forwardInvocation:方法也可以將幾條消息連接成一個(gè)單獨(dú)的響應(yīng)。forwardInvocation:能做的取決于它的實(shí)現(xiàn)娄帖。但是它所提供的鏈接對(duì)象到一條轉(zhuǎn)發(fā)鏈的機(jī)會(huì)為程序設(shè)計(jì)敞開(kāi)了大門也祠。
注意forwardInvocation:方法只有在消息沒(méi)有喚起名義上接收者的存在的方法時(shí)才會(huì)處理這些消息。例如块茁,如果你想要你的對(duì)象轉(zhuǎn)發(fā)negotiate消息到另一個(gè)對(duì)象,你的對(duì)象本身就不能擁有negotiate方法桂肌。如果有的話数焊,消息永遠(yuǎn)也不會(huì)到達(dá)forwardInvocation:
更多關(guān)于轉(zhuǎn)發(fā)和調(diào)用(invocations)的消息崎场,參考invocations

2.轉(zhuǎn)發(fā)和多繼承

轉(zhuǎn)發(fā)模仿(mimics)繼承佩耳,它可以向Objective-C程序添加一些多繼承的效果。如圖3所示谭跨,一個(gè)通過(guò)轉(zhuǎn)發(fā)來(lái)響應(yīng)一條消息的對(duì)象看起來(lái)就像"借來(lái)"或者"繼承"了一個(gè)定義在另一個(gè)類里方法實(shí)現(xiàn)干厚。

圖3:Forwarding

示例中,一個(gè)戰(zhàn)士類的實(shí)例將一條negotiate消息轉(zhuǎn)發(fā)給了一個(gè)外交家類的實(shí)例螃宙。這個(gè)戰(zhàn)士會(huì)像外交家一樣來(lái)談判蛮瞄。這個(gè)戰(zhàn)士看起來(lái)就像響應(yīng)了這條negotiate消息(盡管實(shí)際上是一個(gè)外交家在做這項(xiàng)工作)。
轉(zhuǎn)發(fā)了一條消息的這個(gè)對(duì)象于是乎從繼承體系的兩個(gè)分支"繼承"方法--它自己的分支和響應(yīng)這條消息的對(duì)象谆扎。上面的例子中挂捅,這個(gè)戰(zhàn)士類看起來(lái)像是把外交家類當(dāng)做父類一樣來(lái)繼承。

3.替代對(duì)象

轉(zhuǎn)發(fā)不只是模仿多繼承堂湖,它同樣可以開(kāi)發(fā)輕量級(jí)的對(duì)象來(lái)代表或者"覆蓋"更加復(fù)雜的對(duì)象(substantial objects)闲先。這個(gè)替代對(duì)象代替其它對(duì)象并向它發(fā)送消息。

4.轉(zhuǎn)發(fā)和繼承

盡管轉(zhuǎn)發(fā)模擬繼承无蜂,NSObject類永遠(yuǎn)也不會(huì)混淆這兩個(gè)概念伺糠。像respondsToSelector:isKindOfClass:這樣的方法只會(huì)考察繼承體系,而不會(huì)考察轉(zhuǎn)發(fā)鏈斥季。例如训桶,如果詢問(wèn)一個(gè)戰(zhàn)士它是否響應(yīng)negotiate消息,

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...

答案是NO酣倾,即使它可以接收negotiate消息渊迁,不報(bào)錯(cuò)并能響應(yīng)它們。
在一些情況下灶挟,NO是正確的答案琉朽。但是有些情況下不是。如果你使用轉(zhuǎn)發(fā)來(lái)創(chuàng)建一個(gè)替代對(duì)象或者擴(kuò)展一個(gè)類的功能稚铣,那么轉(zhuǎn)發(fā)機(jī)制應(yīng)該盡可能和繼承一樣透明箱叁。如果你期望你的對(duì)象就像真的繼承了消息所轉(zhuǎn)發(fā)到的對(duì)象的行為墅垮,你需要重新實(shí)現(xiàn)respondsToSelector:isKindOfClass:方法來(lái)加入你的轉(zhuǎn)發(fā)算法:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;
}

除了respondsToSelector:isKindOfClass:instancesRespondToSelector:方法也應(yīng)該模仿轉(zhuǎn)發(fā)算法耕漱。如果使用了協(xié)議算色,conformsToProtocol:方法同樣需要添加到列表中。同樣的螟够,如果一個(gè)對(duì)象轉(zhuǎn)發(fā)了任何遠(yuǎn)程消息(remote messages)灾梦,它也應(yīng)該實(shí)現(xiàn)methodSignatureForSelector:以便可以返回最終會(huì)響應(yīng)轉(zhuǎn)發(fā)消息的方法的準(zhǔn)確描述;例如妓笙,如果一個(gè)對(duì)象可以轉(zhuǎn)發(fā)一條消息到它的替代若河,你應(yīng)該像下面這樣實(shí)現(xiàn)methodSignatureForSelector:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

注意:這是一門高級(jí)的技術(shù),只適用于沒(méi)有其它解決方案可選的情況寞宫。它并不是為了替換繼承萧福。如果你一定要使用這門技術(shù),確保你完全理解了執(zhí)行轉(zhuǎn)發(fā)的類和轉(zhuǎn)發(fā)到的類的行為辈赋。


六:類型編碼(Type Encodings)

為了支持運(yùn)行時(shí)系統(tǒng)鲫忍,編譯器將每個(gè)方法的返回值和參數(shù)類型編碼到一個(gè)字符串,并將這個(gè)字符串和方法的選擇子關(guān)聯(lián)起來(lái)钥屈。這套編碼方案因?yàn)橥瑯舆m用于其它的上下文環(huán)境悟民,所以設(shè)計(jì)成了一個(gè)公共的編譯器指令:@encode()。當(dāng)傳入一個(gè)類型規(guī)格后篷就,@encode()返回一個(gè)編碼傳入類型所對(duì)應(yīng)的字符逾雄。傳入的類型可以是基本的數(shù)據(jù)類型如int,可以是一個(gè)指針腻脏,一個(gè)標(biāo)記的結(jié)構(gòu)體或者集合鸦泳,或者一個(gè)類名,實(shí)際上永品,任何可以作為sizeof()操作符參數(shù)的都可以傳入做鹰。

char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);

圖4列出了類型編碼對(duì)照。

圖4:Objective-C type encodings

注意:Objective-C不支持long double類型鼎姐,@encode(long double)返回d钾麸,同編碼double類型一樣。
一個(gè)數(shù)組的類型編碼被包圍在一對(duì)方括號(hào)中炕桨;數(shù)組元素的個(gè)數(shù)在左方括號(hào)后立刻被指定饭尝,位于數(shù)組類型之前。例如献宫,一個(gè)包含12個(gè)指向浮點(diǎn)數(shù)的指針會(huì)被編碼成:

[12^f]

結(jié)構(gòu)體被大括號(hào)包圍钥平,集合被圓括號(hào)包圍。首先列出的是結(jié)構(gòu)體的tag姊途,接下來(lái)是一個(gè)等號(hào)涉瘾,最后是結(jié)構(gòu)體各字段的編碼類型知态。

typedef struct example {
    id   anObject;
    char *aString;
    int  anInt;
} Example;

會(huì)被編碼成:

{example=@*i}

傳入@encode()的無(wú)論是定義的類型名(Example)還是結(jié)構(gòu)體的tag(example)結(jié)果都是一樣的。如果是一個(gè)結(jié)構(gòu)體的指針立叛,編碼結(jié)果會(huì)是:

^{example=@*i}

對(duì)象被當(dāng)做結(jié)構(gòu)體處理负敏。例如,傳入NSObject編碼后結(jié)果為:

{NSObject=#}

NSObject類只聲明了一個(gè)實(shí)例變量isa秘蛇,它是一個(gè)Class其做。
注意:對(duì)于圖5的編碼類型,@encode()指令并不返回它們赁还,但是當(dāng)它們被用來(lái)在協(xié)議中聲明方法時(shí)妖泄,運(yùn)行時(shí)系統(tǒng)會(huì)使用它們作為類型限定符。

圖5:Objective-C method encodings


七:聲明屬性

當(dāng)編譯器遇到屬性聲明時(shí)秽浇,它會(huì)生成描述性的元數(shù)據(jù)浮庐,這些元數(shù)據(jù)會(huì)和其所在的類甚负,分類和協(xié)議關(guān)聯(lián)起來(lái)柬焕。可以使用函數(shù)來(lái)訪問(wèn)元數(shù)據(jù)梭域,這些函數(shù)必須支持通過(guò)名字查詢某個(gè)類的某個(gè)屬性斑举,并能獲取到這個(gè)屬性的類型(@encode),而且可以將屬性的屬性列表復(fù)制為一個(gè)C字符串的數(shù)組病涨。每個(gè)類和協(xié)議都有一組聲明屬性的清單富玷。

1.屬性類型和函數(shù)

objc_property_t結(jié)構(gòu)體對(duì)一個(gè)屬性描述符號(hào)定義了一個(gè)不透明的句柄。

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

可以使用class_copyPropertyListprotocol_copyPropertyList函數(shù)分別獲取某個(gè)類(包括加載的類)或者某個(gè)協(xié)議所關(guān)聯(lián)的屬性數(shù)組既穆。

OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
OBJC_EXPORT objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

例如赎懦,考慮以下的類聲明:

@interface Lender : NSObject {
    float alone;
}
@property float alone;
@end

可以使用下面的代碼獲取屬性列表:

id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

使用property_getName可以獲取某個(gè)屬性的名字。

OBJC_EXPORT const char *property_getName(objc_property_t property) 

使用class_getPropertyprotocol_getProperty函數(shù)可以分別獲取某個(gè)類或者協(xié)議的某個(gè)屬性的索引:

OBJC_EXPORT objc_property_t class_getProperty(Class cls, const char *name)
OBJC_EXPORT objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

使用property_getAttributes函數(shù)可以獲取一個(gè)屬性的名字和編碼類型(@encode)幻工。

OBJC_EXPORT const char *property_getAttributes(objc_property_t property) 

結(jié)合以上方法励两,可以打印某個(gè)類的所有屬性列表:

id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
///打印結(jié)果
quartzView T@"TestView",W,N,V_quartzView
mStub T@"Stub",&,N,V_mStub

2.屬性類型字符

使用property_getAttributes函數(shù)可以獲取一個(gè)屬性的名字、編碼類型(@encode)和其它的特性(attributes)囊颅。
它返回字符串以T開(kāi)頭当悔,接下來(lái)是@encode類型、逗號(hào)踢代、V盲憎、實(shí)例變量名。在逗號(hào)和V中間是下圖的描述符胳挎,以逗號(hào)分隔饼疙。

圖6:Declared property type encodings

3.屬性特性描述符實(shí)例

考慮以下的定義:

enum FooManChu { FOO, MAN, CHU };
struct YorkshireTeaStruct { int pot; char lady; };
typedef struct YorkshireTeaStruct YorkshireTeaStructType;
union MoneyUnion { float alone; double down; };

下圖展示了示例屬性聲明和對(duì)應(yīng)property_getAttributes的返回值:

圖7


參考文獻(xiàn):Objective-C Runtime Programming Guide



提升代碼質(zhì)量最神圣的三部曲:模塊設(shè)計(jì)(謀定而后動(dòng)) -->無(wú)錯(cuò)編碼(知止而有得) -->開(kāi)發(fā)自測(cè)(防患于未然)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慕爬,隨后出現(xiàn)的幾起案子宏多,更是在濱河造成了極大的恐慌儿惫,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伸但,死亡現(xiàn)場(chǎng)離奇詭異肾请,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)更胖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門铛铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人却妨,你說(shuō)我怎么就攤上這事饵逐。” “怎么了彪标?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵倍权,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我捞烟,道長(zhǎng)薄声,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任题画,我火速辦了婚禮默辨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苍息。我一直安慰自己缩幸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布竞思。 她就那樣靜靜地躺著表谊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盖喷。 梳的紋絲不亂的頭發(fā)上爆办,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音传蹈,去河邊找鬼押逼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惦界,可吹牛的內(nèi)容都是我干的挑格。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沾歪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼漂彤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挫望,失蹤者是張志新(化名)和其女友劉穎立润,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體媳板,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桑腮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛉幸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片破讨。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奕纫,靈堂內(nèi)的尸體忽然破棺而出提陶,到底是詐尸還是另有隱情,我是刑警寧澤匹层,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布隙笆,位于F島的核電站,受9級(jí)特大地震影響升筏,放射性物質(zhì)發(fā)生泄漏撑柔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一仰冠、第九天 我趴在偏房一處隱蔽的房頂上張望乏冀。 院中可真熱鬧蝶糯,春花似錦洋只、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至妒茬,卻和暖如春担锤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乍钻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工肛循, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人银择。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓多糠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親浩考。 傳聞我的和親對(duì)象是個(gè)殘疾皇子夹孔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容