? ? ? ? ? ? ? ? ? ? ? ? ? ?iOS Runtime 消息轉(zhuǎn)發(fā)機(jī)制原理
最近看到很多面試問道 runtime是個(gè)啥,咋實(shí)現(xiàn)的撒穷、具體實(shí)現(xiàn)的步驟勾笆,針對(duì)此類問題我做了一下整理,可能幫到小伙伴的可以看看桥滨。廢話不多說直接走起
我們?cè)陂_發(fā)過程中窝爪,經(jīng)常會(huì)遇到這樣的報(bào)錯(cuò),當(dāng)我們調(diào)用一個(gè)對(duì)象不存在的方法的時(shí)候
系統(tǒng)報(bào)錯(cuò) 提示如下錯(cuò)誤
"-[YBHomeHeaderCRView pushMessage:]: unrecognized selector sent to instance 0x7fc77cf0de40"
其實(shí)這種報(bào)錯(cuò)都是iOS消息轉(zhuǎn)發(fā)機(jī)制在無法響應(yīng)的情況后拋出的問題齐媒。那我們來深入的看一下這個(gè)消息機(jī)制是這么走的蒲每。
這么進(jìn)入的消息機(jī)制的
1.首先通過[YBHomeHeaderCRView new]對(duì)象的ISA指針找打它對(duì)應(yīng)的class。
2.首先在class的cache用查找是否有sendMessage方法喻括,沒有再去class的method list 查找邀杏,找到并將其加入到cache中,方便下次調(diào)用唬血。
3.如果沒有就去它的superclass里繼續(xù)查找望蜡。也是先cache,在methodlists拷恨。
4.一旦查找到對(duì)應(yīng)的函數(shù)方法脖律,通過函數(shù)指針I(yè)MP調(diào)轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行。
5.如果一直沒有找到腕侄,就執(zhí)行消息轉(zhuǎn)發(fā)小泉。
消息機(jī)制原理
我們先看一下下面這個(gè)結(jié)構(gòu)圖芦疏,先對(duì)整個(gè)消息處理機(jī)制有一個(gè)初步的認(rèn)識(shí)
從結(jié)構(gòu)圖來看,消息轉(zhuǎn)發(fā)機(jī)制共分為3大步驟:
1.Method resolution 動(dòng)態(tài)方法解析處理階段
2.Fast forwarding 快速轉(zhuǎn)發(fā)階段
3.Normal forwarding 慢速轉(zhuǎn)發(fā)階段
如果想要不拋出unrecognized selector 的報(bào)錯(cuò)微姊,也就需要從這3步里面來做補(bǔ)救酸茴。
第一步?動(dòng)態(tài)方法解析處理階段
如果調(diào)用了對(duì)象方法首先會(huì)進(jìn)行+(BOOL)resolveInstanceMethod:(SEL)sel判斷
如果調(diào)用了類方法 首先會(huì)進(jìn)行 +(BOOL)resolveClassMethod:(SEL)sel判斷
如果YES則能接受消息,NO則不能接受消息 則就回進(jìn)入第二步
我們先調(diào)一下對(duì)象方法兢交,然后在resolveInstanceMethod進(jìn)行補(bǔ)救薪捍,
void pushMessage(id self, SEL _cmd, NSString *msg){
? ? NSLog(@"------%@",msg);
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
? ? NSString *methodName =NSStringFromSelector(sel);
? ? if ([methodName isEqualToString:@"pushMessage:"]) {
? ? ? ? return class_addMethod(self, sel, (IMP)pushMessage, "v@:@");
? ? }
? ? return? NO;
}
經(jīng)過上面類型的補(bǔ)救,果然對(duì)象方法不在拋出異常了配喳,并且打印了數(shù)據(jù)
第二歩:快速轉(zhuǎn)發(fā)階段
如果在上一步的方法內(nèi)返回的為YES則能接受消息 NO不能接受消息 則進(jìn)入第二步酪穿,我們先把上面方法內(nèi)的處理方案注釋掉,讓消息轉(zhuǎn)發(fā)進(jìn)入第二步界逛。
我們新創(chuàng)建一個(gè)YBMessage類昆稿,里面聲明和實(shí)現(xiàn)pushMessage方法纺座,用來當(dāng)作備用響應(yīng)者息拜。
-(id)forwardingTargetForSelector:(SEL)aSelector{
? ? NSString *methodName =NSStringFromSelector(aSelector);
? ? if ([methodName isEqualToString:@"pushMessage:"]) {
? ? ? ? return [YBMessage new];
? ? }
? ? return [super forwardingTargetForSelector:aSelector];
}
因?yàn)橐粋€(gè)對(duì)象內(nèi)部可能還有其他可能響應(yīng)的對(duì)象,所以這個(gè)方法是轉(zhuǎn)發(fā)SEL去對(duì)象內(nèi)部的其他可以響應(yīng)該方法的對(duì)象净响。
第三歩 慢速轉(zhuǎn)發(fā)階段
如果第2步返回self或者nil,則說明沒有可以響應(yīng)的目標(biāo) 則進(jìn)入第三步少欺。第三步的消息轉(zhuǎn)發(fā)機(jī)制本質(zhì)上跟第二步是一樣的都是切換接受消息的對(duì)象,但是第三步切換響應(yīng)目標(biāo)更復(fù)雜一些馋贤,需要手動(dòng)將響應(yīng)方法切換給備用響應(yīng)對(duì)象赞别。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
? ? NSString*methodName =NSStringFromSelector(aSelector);
? ? if([methodNameisEqualToString:@"pushMessage:"]) {
? ? ? ? return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
? ? }
? ? return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
? ? SELsel = [anInvocationselector];
? ? YBMessage *msg = [YBMessage new];
? ? if([msg isEqualToString:sel]) {
? ? ? ? [anInvocationinvokeWithTarget:msg];
? ? }else{
? ? ? ? [superforwardInvocation:anInvocation];
? ? }
}
//例子"v@:@"
//v@:@ v:返回值類型void;@ id類型,執(zhí)行sel的對(duì)象;:SEL;@參數(shù)
如果前三個(gè)都沒有找到方法
- (void)doesNotRecognizeSelector:(SEL)aSelector{
? ? NSLog(@"找不到方法");
}
同時(shí),越往后面處理代價(jià)越高配乓,最好的情況是在第一步就處理消息仿滔,這樣runtime會(huì)在處理完后緩存結(jié)果,可以提高處理效率犹芹。
總結(jié):
在一個(gè)函數(shù)找不到時(shí)崎页,OC提供了三種方式去補(bǔ)救:
1、調(diào)用resolveInstanceMethod給個(gè)機(jī)會(huì)讓類添加這個(gè)實(shí)現(xiàn)這個(gè)函數(shù)
2腰埂、調(diào)用forwardingTargetForSelector讓別的對(duì)象去執(zhí)行這個(gè)函數(shù)
3飒焦、調(diào)用forwardInvocation(函數(shù)執(zhí)行器)靈活的將目標(biāo)函數(shù)以其他形式執(zhí)行。
如果都不中屿笼,調(diào)用doesNotRecognizeSelector拋出異常牺荠。