前言
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è)版本:legacy
和modern
- 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)境的工具滑绒。
三:消息傳遞(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)建你自己的根類,繼承自NSObject
或NSProxy
的對(duì)象自動(dòng)擁有這個(gè)isa
指針家制。
類和對(duì)象的元素如圖2所示正林。
當(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)銷。
使用NSObject
的methodForSelector:
方法茴肥,你可以請(qǐng)求一個(gè)指向?qū)崿F(xiàn)了一個(gè)方法的procedure
坚踩,然后使用這個(gè)指針來(lái)調(diào)用procedure
。methodForSelector:
返回的指針必須被小心的轉(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ě)NSObject
的forwardInvocation:
方法滑频,你可以充分利用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)干厚。
示例中,一個(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ì)照。
注意: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ì)使用它們作為類型限定符。
七:聲明屬性
當(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_copyPropertyList
和protocol_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_getProperty
和protocol_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)分隔饼疙。
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
的返回值:
參考文獻(xiàn):Objective-C Runtime Programming Guide
提升代碼質(zhì)量最神圣的三部曲:模塊設(shè)計(jì)(謀定而后動(dòng)) -->無(wú)錯(cuò)編碼(知止而有得) -->開(kāi)發(fā)自測(cè)(防患于未然)