函數(shù)的調(diào)用方式
Objective-C是C語(yǔ)言的超集褥蚯,C語(yǔ)言的函數(shù)調(diào)用方式是“靜態(tài)綁定的”挚冤,也就是說(shuō)在編譯的時(shí)候就知道運(yùn)行時(shí)要調(diào)用什么函數(shù),如果調(diào)用一個(gè)未聲明的函數(shù)赞庶,編譯期間就會(huì)報(bào)錯(cuò)训挡。
void printGoodMorning(){
printf("good morning,huang");
}
void printGoodAfternoon(){
prinf("good afternoon,huang");
}
void helloHuang(int time){
if(time > 6 && time < 12){
printGoodMorning();
}else if(time < 18){
printGoodAfternoon();
}else{
printHelloAllTheTime();//error here
}
}
但是如果我們把上面這段代碼改寫(xiě)一下,如下
void printGoodMorning(){
printf("good morning,huang");
}
void printGoodAfternoon(){
prinf("good afternoon,huang");
}
void helloHuang(int time){
void (*hello)();
if(time > 6 && time < 12){
hello = printGoodMorning;
}else if(time < 18){
hello = printGoodAfternoon;
}
hello()
}
你會(huì)發(fā)現(xiàn)只有當(dāng)程序運(yùn)行起來(lái)之后尘执,才能知道要執(zhí)行哪個(gè)函數(shù)舍哄,這就得使用“動(dòng)態(tài)綁定”的概念了
Objective-C是使用傳遞消息的機(jī)制來(lái)調(diào)用函數(shù),這就會(huì)使用到動(dòng)態(tài)綁定的機(jī)制在運(yùn)行期來(lái)決定到底調(diào)用哪個(gè)方法誊锭,甚至我們可以在運(yùn)行時(shí)改變對(duì)象調(diào)用的方法表悬。
對(duì)象發(fā)送消息大概是這樣的:
id returnType = [object messageName:parameter];
編譯器會(huì)轉(zhuǎn)化為我們斷點(diǎn)堆棧中經(jīng)常能看到的下面這樣的C函數(shù):
void objc_msgSend(id self, SEL cmd,...)
這是個(gè)參數(shù)可以變化的函數(shù),第一個(gè)參數(shù)代表了消息的接收者丧靡,第二個(gè)參數(shù)是所需要執(zhí)行的方法名蟆沫,后面是消息的參數(shù)籽暇。
objc_msgSend是如何依據(jù)消息的接收者和方法名找到合適的方法呢?
首先饭庞,需要在接收者所屬的類(lèi)中的方法列表中去查找這個(gè)方法戒悠,找到了直接調(diào)用,沒(méi)找到則一級(jí)一級(jí)便利去基類(lèi)的方法列表舟山,直到NSObject绸狐。如果沒(méi)找到,那么就會(huì)觸發(fā)“消息轉(zhuǎn)發(fā)”機(jī)制累盗。
消息轉(zhuǎn)發(fā)機(jī)制如果處理妥當(dāng)寒矿,那么這條消息則能正常處理,如果處理不善若债,或者根本沒(méi)處理符相,那么就會(huì)看到我們常看到的崩潰:
-[xxxobject xxxfunc]: unrecognized selector sent to instance 0xxx
簡(jiǎn)單的解決方案就是蠢琳,去對(duì)應(yīng)的類(lèi)添加對(duì)應(yīng)的方法就行了啊终。但是現(xiàn)在我們要提到的是,怎么動(dòng)態(tài)的使用消息轉(zhuǎn)發(fā)來(lái)處理這個(gè)問(wèn)題傲须。
消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)有下面幾個(gè)階段
- “動(dòng)態(tài)方法解析”蓝牲,征詢接收者所屬的類(lèi),是否需要?jiǎng)討B(tài)添加方法躏碳,來(lái)處理這個(gè)未找到的方法搞旭。
- “備用接受者”,轉(zhuǎn)發(fā)給其他的對(duì)象處理這個(gè)方法菇绵。
- “完整的消息轉(zhuǎn)發(fā)機(jī)制”肄渗,如果未聲明其他對(duì)象處理,或者其他對(duì)象處理失敗了咬最,那么系統(tǒng)就會(huì)把消息所有相關(guān)的封裝成一個(gè)NSInvocation對(duì)象翎嫡,我們可以拿到這個(gè)NSInvocation對(duì)象,addTarget指明方法的處理者永乌。
下面我們通過(guò)一個(gè)demo來(lái)理解下消息轉(zhuǎn)發(fā)
新建一個(gè)Person的類(lèi)
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
- (instancetype)init:(NSString *)name;
- (void)eat;
- (void)sleep;
@end
這個(gè)類(lèi)有一個(gè)init方法和兩個(gè)實(shí)例方法惑申,對(duì)應(yīng).m都有實(shí)現(xiàn)。
#import "Person.h"
@implementation Person
- (instancetype)init:(NSString *)name{
self = [super init];
if (self) {
_name = name;
}
return self;
}
- (void)eat{
NSLog(@"i love fish");
}
- (void)sleep{
NSLog(@"good night");
}
@end
現(xiàn)在我們?cè)賱?chuàng)建一個(gè)Developer的類(lèi)繼承于Person
#import "Person.h"
@interface Developer : Person
- (void)developerCoding;
- (void)developerDebug;
@end
.m里面實(shí)現(xiàn)了developer前綴的兩個(gè)方法
#import "Developer.h"
@implementation Developer
- (instancetype)init:(NSString *)name{
self = [super init:name];
if (self) {
}
return self;
}
- (void)developerCoding{
NSLog(@"class:%@, sel:%s",self,sel_getName(_cmd));
NSLog(@"i hate pm");
}
- (void)developerDebug{
NSLog(@"class:%@, sel:%s",self,sel_getName(_cmd));
NSLog(@"i hate bug");
}
@end
這個(gè)時(shí)候翅雏,我們創(chuàng)建一個(gè)Person的實(shí)例對(duì)象圈驼,并讓它做一些事情
Person *person = [[Person alloc] init:@"yuan"];
[person eat];
[person sleep];
[person performSelector:@selector(developerCoding)];
[person performSelector:@selector(developerDebug)];
本來(lái)這個(gè)人實(shí)例吃吃睡睡挺滋潤(rùn)的,但是為了不荒廢人生望几,還是該干點(diǎn)活的绩脆,所以除了吃和睡啦逆,強(qiáng)制讓他去寫(xiě)代碼改bug恕洲。
但是你會(huì)發(fā)現(xiàn)屈梁,強(qiáng)制他去做他不會(huì)的活盐碱,他就要革命了(crash)。在不做任何處理的情況下玉锌,這個(gè)人根本完成不了編程和解bug的活名挥。所以作為組織領(lǐng)導(dǎo),我們要教導(dǎo)下面的人主守,接到完不成的工作禀倔,應(yīng)該積極響應(yīng),而不是搞什么革命参淫。
所以某位同志做不了這個(gè)活的時(shí)候蹋艺,我們教導(dǎo)他說(shuō):你應(yīng)該這樣做。
首先黄刚,當(dāng)發(fā)現(xiàn)不能干這個(gè)活,那么你可以現(xiàn)學(xué)呀民效。
//消息轉(zhuǎn)發(fā)第一步 動(dòng)態(tài)方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selName = NSStringFromSelector(sel);
if ([selName hasPrefix:@"developer"]) {
class_addMethod(self, sel, (IMP)shouldDoSomeThing, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//動(dòng)態(tài)將實(shí)現(xiàn)轉(zhuǎn)到這個(gè)函數(shù)
void shouldDoSomeThing(id self ,SEL _cmd){
NSLog(@"class:%@, sel:%s",self,sel_getName(_cmd));
NSLog(@"maybe i should do something like a developer");
}
給類(lèi)動(dòng)態(tài)創(chuàng)建一個(gè)方法實(shí)現(xiàn)憔维,然后處理這個(gè)原本無(wú)法響應(yīng)的方法。這步完成畏邢,消息得到處理业扒,結(jié)束。
如果你是個(gè)懶人舒萎,不愿意學(xué)這個(gè)編程程储,那怎么辦呢,那學(xué)會(huì)甩鍋呀臂寝。
//消息轉(zhuǎn)發(fā)第二步 備選接收者
- (id)forwardingTargetForSelector:(SEL)aSelector{
Developer *dev = [[Developer alloc] init:@"Huang"];
if ([dev respondsToSelector:aSelector]) {
return dev;
}
return [super forwardingTargetForSelector:aSelector];
}
去找了一個(gè)專(zhuān)業(yè)的開(kāi)發(fā)人員章鲤,來(lái)處理這個(gè)這個(gè)問(wèn)題,消息被轉(zhuǎn)發(fā)給了Developer的實(shí)例咆贬,得到了處理败徊,結(jié)束。
如果既不動(dòng)態(tài)生成方法實(shí)現(xiàn)掏缎,也不轉(zhuǎn)發(fā)給能處理的備選接收者皱蹦,最后還有一個(gè)解決方案。那你寫(xiě)一個(gè)方法的詳細(xì)報(bào)告眷蜈,對(duì)組織說(shuō)明這個(gè)活的詳細(xì)細(xì)節(jié)是什么沪哺,上交這份報(bào)告(NSInvocation)給組織,組織給你處理酌儒。
//消息轉(zhuǎn)發(fā)第三部 完整的消息轉(zhuǎn)發(fā)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if ([Developer instancesRespondToSelector:anInvocation.selector]) {
Developer *dev = [[Developer alloc] init:@"Huang"];
[anInvocation invokeWithTarget:dev];
}
}
//person找不到developer相關(guān)的方法辜妓,就是因?yàn)檫@個(gè)函數(shù)找不到方法的實(shí)現(xiàn)簽名引發(fā)了崩潰,我們這里需要給aSelector新建方法簽名 再交給Developer對(duì)象去處理
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
//不簽名會(huì)崩潰
//判斷實(shí)現(xiàn)類(lèi)的實(shí)例是否有這個(gè)方法 有則簽名這個(gè)方法 保證能正確轉(zhuǎn)發(fā)
if ([Developer instancesRespondToSelector:aSelector]) {
signature = [Developer instanceMethodSignatureForSelector:aSelector];
}
//直接簽名
// if ([NSStringFromSelector(aSelector) hasPrefix:@"developer"]) {
// signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
// }
}
return signature;
}
組織要處理呀,不處理就革命了嫌拣。拿到方法的細(xì)節(jié)封裝NSInvocation對(duì)象之前柔袁,組織需要知道為啥會(huì)上傳報(bào)告,通過(guò)methodSignatureForSelector异逐,發(fā)現(xiàn)組織的科技樹(shù)(方法簽名)里面沒(méi)有developer相關(guān)的技能捶索,所以干不了這個(gè)活。那么既然科技樹(shù)里面沒(méi)有developer相關(guān)技能灰瞻,那么我們就要先點(diǎn)亮科技樹(shù)才行腥例,所以我們給developer前綴的方法進(jìn)行方法簽名。點(diǎn)亮科技樹(shù)酝润。
然后forwardInvocation中燎竖,把報(bào)告交給能干這個(gè)活的人,完成消息轉(zhuǎn)發(fā)要销,結(jié)束构回。
根據(jù)實(shí)際的情況決定怎么處理NSInvocation對(duì)象,本Demo只是簡(jiǎn)單地把NSInvocation交給了備選接收者處理疏咐。
總結(jié)
- 如果一個(gè)對(duì)象無(wú)法響應(yīng)一個(gè)方法纤掸,那么就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制
- 第一步,我們可以動(dòng)態(tài)創(chuàng)建一個(gè)方法實(shí)現(xiàn)去響應(yīng)這個(gè)消息浑塞,消息轉(zhuǎn)發(fā)結(jié)束
- 第一步未處理借跪,第二步我們可以選擇一個(gè)備選的消息接收者去處理這個(gè)消息
- 第二步未實(shí)現(xiàn),最后一步第三步酌壕,啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制掏愁,處理方法簽名,實(shí)現(xiàn)NSInvocation對(duì)象轉(zhuǎn)發(fā)卵牍。
- 最好在第一步處理果港,次之第二步,如果第三步只是使用備選接收者處理辽慕,還不如直接第二步快速處理結(jié)束京腥,畢竟第三步需要方法簽名和封裝NSInvocation。