大家都知道,OC是一門(mén)動(dòng)態(tài)語(yǔ)言 ,傳統(tǒng)的靜態(tài)語(yǔ)言如C語(yǔ)言,在編譯的時(shí)候已經(jīng)決定了要調(diào)用的方法。而對(duì)于像OC這樣的動(dòng)態(tài)語(yǔ)言,由于有runtime機(jī)制,所以編譯階段并不知道要調(diào)用的具體方法實(shí)現(xiàn),在運(yùn)行的時(shí)候才會(huì)去調(diào)用,這也導(dǎo)致了一些case比如給某個(gè)target指定了action的SEL,但是沒(méi)有對(duì)這個(gè)SEL對(duì)應(yīng)的方法做具體實(shí)現(xiàn),在編譯的時(shí)候非常No Problem,但是在運(yùn)行的時(shí)候就會(huì)因?yàn)闆](méi)有找到對(duì)應(yīng)的實(shí)現(xiàn)而crash。而這種情況也是我們經(jīng)常會(huì)碰到的。
那么該怎么處理這種case呢?
研究了runtime的一些原理以及OC對(duì)象的內(nèi)存布局 ,我們可以得出這么幾個(gè)結(jié)論:
1、OC對(duì)象調(diào)用方法會(huì)用到選擇器selector(也就是SEL),通過(guò)SEL來(lái)找到具體的實(shí)現(xiàn)IMP
2、SEL是具體方法的一個(gè)索引,IMP是具體實(shí)現(xiàn)的函數(shù)指針,兩者配合可以快速找到具體實(shí)現(xiàn)的方法并調(diào)用
3、每個(gè)OC的類其實(shí)就是個(gè)結(jié)構(gòu)體,查看objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下
struct objc_class
{
struct objc_class* isa;//isa指針指向本類
struct objc_class* super_class;//父類指針,可以根據(jù)此找到父類
const char* name;//類名
long version;//類的版本信息,默認(rèn)為0(一般不怎么關(guān)注)
long info;//類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size;// 該類的實(shí)例變量大小
struct objc_ivar_list* ivars;//成員變量列表
struct objc_method_list** methodLists;//方法列表
struct objc_cache* cache;//方法緩存
struct objc_protocol_list* protocols;//協(xié)議方法列表
};
4、對(duì)象查找方法實(shí)現(xiàn)的過(guò)程:
1 先到cache方法緩存中找,有則調(diào)用,沒(méi)有則下一步
2 到methodLists方法列表中找,有則調(diào)用,沒(méi)有則下一步
3 本類中沒(méi)找到,就到super_class的methodLists中找,若還沒(méi)有,就一直往上找,找到最頂層還沒(méi)有的話就crash了。
但是在crash之前,其實(shí)有3個(gè)消息轉(zhuǎn)發(fā)的補(bǔ)救方式,來(lái)防止crash。當(dāng)然若沒(méi)有采取什么"補(bǔ)救方式",就直接crash了。這三個(gè)補(bǔ)救方式簡(jiǎn)單講就是:
1 動(dòng)態(tài)向類中加入缺失的方法
2 指定處理消息的接受者(備用的接受者,就像快遞電話來(lái)了你沒(méi)空,可以找同學(xué)來(lái)幫忙簽收一下)
3 使用NSInvocation進(jìn)行消息轉(zhuǎn)發(fā)
這三種補(bǔ)救方法涉及到的API有:
//動(dòng)態(tài)方法解析
+(BOOL) resolveInstanceMethod:(SEL)selector
+(BOOL)resolveClassMethod:(SEL)sel
//備用接收者
-(id)forwardingTargetForSelector:(SEL)aSelector
//NSInvocation進(jìn)行消息轉(zhuǎn)發(fā)
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation
并且runtime會(huì)依次按以上順序調(diào)用,去看你是否有補(bǔ)救。
我們先人為造一個(gè)crash的場(chǎng)景:
建立一個(gè)Person類,Person.h
#import@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,strong)NSNumber *age;
@property(nonatomic,weak)id delegate;
@end
Person.m
#import "Person.h"
@implementation Person
@end
在控制器中 創(chuàng)建一個(gè)Person對(duì)象,
Person *pson = [[Person alloc] init];
pson.name = @"wc";
pson.age = @99;
[pson performSelector:@selector(eat) withObject:nil];
顯然這里我們并沒(méi)有時(shí)間eat方法,程序未找到eat的具體實(shí)現(xiàn)而直接崩潰。
補(bǔ)救措施1:
在+(BOOL) resolveInstanceMethod:(SEL)selector中動(dòng)態(tài)添加eat方法。
+(BOOL) resolveInstanceMethod:(SEL)selector
{
NSString *selectorStr = NSStringFromSelector(selector);
if ([selectorStr isEqualToString:@"eat"])
{
class_addMethod(self, selector, (IMP)eat, "v@:");
return YES;
}
return [self resolveClassMethod:selector];
}
具體實(shí)現(xiàn):
void eat()
{
NSLog(@"person eat");
}
補(bǔ)救措施2:使用備用接收者,此時(shí),回調(diào)用這個(gè)方法-(id)forwardingTargetForSelector:(SEL)aSelector,這里為peson增加一個(gè)代理屬性,將person沒(méi)有實(shí)現(xiàn)的方法,交給代理去做
#import@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,strong)NSNumber *age;
//代理,將person沒(méi)有實(shí)現(xiàn)的方法,交給代理去做
@property(nonatomic,weak)id delegate;
@end
//為pson對(duì)象添加了代理
Person *pson = [[Person alloc] init];
pson.name = @"wc";
pson.age = @99;
pson.delegate = self;
[pson performSelector:@selector(eat) withObject:nil];
//代理實(shí)現(xiàn)eat方法
-(void)eat
{
NSLog(@"viewcontroller eat");
}
//這里返回代理,讓代理去完成
-(id)forwardingTargetForSelector:(SEL)aSelector
{
return _delegate;
}
當(dāng)然這里不一定要用到代理,只要是你在這個(gè)上下文中可以訪問(wèn)到的合適的對(duì)象就行。
如果到這一步還沒(méi)有采取補(bǔ)救措施,只能啟動(dòng)完整地消息轉(zhuǎn)發(fā)機(jī)制了。
補(bǔ)救措施3:
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation
默認(rèn)的-(void)forwardInvocation:(NSInvocation *)anInvocation的實(shí)現(xiàn)是調(diào)用-(void)doesNotRecognizeSelector:(SEL)aSelector方法,在該方法中將會(huì)拋出異常,導(dǎo)致程序崩潰。
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = nil;
NSString *selectorStr = NSStringFromSelector(aSelector);
if ([selectorStr isEqualToString:@"eat"])
{
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
//方法簽名
return methodSignature;
}
return [self methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwordInvocation");
SEL selector = anInvocation.selector;
if ([_delegate respondsToSelector:selector])
{
[anInvocation invokeWithTarget:_delegate];
}
else
{
return[self forwardInvocation:anInvocation];
}
}
這里消息轉(zhuǎn)發(fā),完全實(shí)現(xiàn)交給了我代理去做了。
參考鏈接:http://blog.csdn.net/liangliang103377/article/details/39007683