oc 是一門動(dòng)態(tài)語言,在編譯的過程中并不能確定變量的所屬類型,也不能確定真正調(diào)用哪個(gè)函數(shù)。只有在運(yùn)行時(shí)才能確定姐直。實(shí)現(xiàn) oc 運(yùn)行時(shí)機(jī)制的基礎(chǔ)就是 runtime
方法調(diào)用的三個(gè)步驟:
1、在編譯時(shí)轉(zhuǎn)化為 C 函數(shù) obj_msgSend(receicer, selector)蒋畜。先在本類的cache中查找方法声畏,找不到去該類的objc_method_list列表中去查找,找不到會遞歸去父類中查找百侧。
2砰识、父類中也沒有找到能扒,會進(jìn)行動(dòng)態(tài)方法解析佣渴,是否動(dòng)態(tài)為該類添加了該方法。
3初斑、沒有動(dòng)態(tài)添加辛润,會進(jìn)行消息轉(zhuǎn)發(fā),崩潰前的補(bǔ)救措施
消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)機(jī)制分為3個(gè)步驟
1见秤、Method resolution 方法解析
在調(diào)用無法解讀(沒有實(shí)現(xiàn))的消息后砂竖,首先會調(diào)用對象方法 resolveInstanceMethod: 或者類方法 resolveClassMethod: 詢問是否有動(dòng)態(tài)添加方法來進(jìn)行處理, 如果返回 YES 則能接受鹃答,返回 NO 進(jìn)入第二步乎澄。
新建一個(gè)Dog類,.h中暴露run方法测摔,.m中不去實(shí)現(xiàn)置济。
@interface Dog : NSObject
- (void)run;
@end
當(dāng)我們調(diào)用run方式時(shí)
Dog *dog = [Dog new];
[dog run];
會發(fā)生崩潰
reason: '-[Dog run]: unrecognized selector sent to instance 0x6000031d5940'
這時(shí)候我們導(dǎo)入<objc/runtime.h>,重寫 resolveInstanceMethod: 動(dòng)態(tài)添加該方法锋八,運(yùn)行不會發(fā)生崩潰浙于,并打印resolveInstanceMethod----------add run Method
返回YES時(shí),會繼續(xù)去方法列表中查找挟纱,沒找到的話繼續(xù)進(jìn)入消息轉(zhuǎn)發(fā)羞酗,如果返回YES,但沒有動(dòng)態(tài)添加該方法紊服,會導(dǎo)致不斷的循環(huán)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(run)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self){
NSLog(@"resolveInstanceMethod----------add run Method");
}), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
v-void檀轨,@-id胸竞,: - _cmd的類型SEL,官方文檔
2裤园、Fast forwarding 快速轉(zhuǎn)發(fā)
調(diào)用 forwardingTargetForSelector: 方法撤师,是否有備用接受者,讓實(shí)現(xiàn)該方法的其他類去處理拧揽;如果沒有備用接受者剃盾,執(zhí)行第三步。
另創(chuàng)建Person類淤袜,實(shí)現(xiàn)run方法痒谴。
- (void)run {
NSLog(@"Person------run");
}
重寫 forwardingTargetForSelector: 方法,并返回 Person 對象铡羡,運(yùn)行程序积蔚,打印 Person------run
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [Person new];
}
return nil;
}
3、Normal forwarding常規(guī)轉(zhuǎn)發(fā)
先調(diào)用 methodSignatureForSelector: 返回SEL方法的簽名
再調(diào)用 forwardInvocation: 進(jìn)入消息轉(zhuǎn)發(fā)最后一步烦周,創(chuàng)建備用對象尽爆,判斷備用對象是否可以響應(yīng)SEL,如果不能響應(yīng)读慎,則拋出異常漱贱。
方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *m = [super methodSignatureForSelector:aSelector];
if (!m) {
m = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return m;
}
將不能識別的消息轉(zhuǎn)發(fā)給其他對象,可連續(xù)轉(zhuǎn)發(fā)給多個(gè)對象夭委,運(yùn)行輸出 Person------run
- (void)forwardInvocation:(NSInvocation *)anInvocation {
Person *p = [Person new];
if([p respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:p];
}else {
[self doesNotRecognizeSelector:anInvocation.selector];
}
}
實(shí)戰(zhàn)
1幅狮、防止未實(shí)現(xiàn)方法而崩潰
#import "NSObject+CrashHandle.h"
#import <AppKit/AppKit.h>
@implementation NSObject (CrashHandle)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"未實(shí)現(xiàn)該方法---%@", NSStringFromSelector(anInvocation.selector));
}
2、模擬多繼承
3株灸、蘋果系統(tǒng)API迭代造成API不兼容的奔潰處理
Method Swizzing 黑魔法
Method Swizzing 是發(fā)生在運(yùn)行時(shí)的崇摄,可以調(diào)用 class_replaceMethod()、method_exchangeImplementations 將兩個(gè) Method 進(jìn)行交換
可以解決防數(shù)組越界慌烧,防止短時(shí)間內(nèi)多次點(diǎn)擊事件(button點(diǎn)擊事件交換系統(tǒng)方法sendAction:to:forEvent:)逐抑、埋點(diǎn)等問題
參考:http://www.reibang.com/p/9263720cbd91
參考:https://juejin.im/post/5ae96e8c6fb9a07ac85a3860