如果我們調(diào)用了一個類沒有的方法锅必,就會進入消息處理機制,有下面幾個階段
“動態(tài)方法解析”:
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
征詢接收者所屬的類杨箭,是否需要動態(tài)添加類方法或?qū)嵗椒ǎ瑏硖幚磉@個未找到的方法娱两。
1). 首先判斷是否實現(xiàn)了 resolveInstanceMethod键科,如果沒有實現(xiàn),進入下一步處理挠锥;
2). 如果實現(xiàn)了众羡,調(diào)用 resolveInstanceMethod,獲取返回值蓖租;
3). 如果返回值為 YES粱侣,表示 resolveInstanceMethod 聲稱它已經(jīng)提供了 selector 的實現(xiàn),因此再次查找 method list蓖宦,如果找到對應(yīng)的 IMP齐婴,則返回該實現(xiàn),否則提示警告信息稠茂,進入下一步處理柠偶;
4). 如果返回值為 NO,進入下一步處理睬关;“重定向”:
- (id)forwardingTargetForSelector:(SEL)aSelector;
如果沒有動態(tài)添加方法诱担,則會進入此階段,此時詢問是否要將這條消息轉(zhuǎn)發(fā)給其他的對象电爹,來處理這個方法该肴。如果返回nil,即表示不轉(zhuǎn)發(fā)給其他對象藐不,此時會進入第3階段“消息轉(zhuǎn)發(fā)”:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
此時系統(tǒng)會根據(jù)SEL詢問方法簽名匀哄,即調(diào)用methodSignatureForSelector:
方法獲取方法簽名,如果這個方法返回nil
雏蛮,那么就會看到我們最常見的一種crash-[Class xxx]: unrecognized selector sent to instance ...
涎嚼。
如果這個方法返回了正確的方法簽名,系統(tǒng)就會根據(jù)消息的SEL挑秉、target法梯、參數(shù)、方法簽名犀概,把消息封裝成一個NSInvocation對象立哑,我們可以拿到這個NSInvocation對象,指定對應(yīng)的target調(diào)用方法姻灶。
這里可以不調(diào)用Invocation铛绰,并不會觸發(fā)crash
1.動態(tài)解析
下面我們通過一個demo來理解下動態(tài)解析,新建一個Person的類
//Person.h
@interface Person : NSObject
//在.m文件中添加了@dynamic产喉,所以不會自動添加setter和getter方法
@property(assign,nonatomic)NSInteger weight;
@end
//Person.m
@implementation Person
@dynamic weight;
void setPropertyDynamic(id self,SEL _cmd){
NSLog(@"This is a dynamic method added for Person instance");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(setWeight:)){
class_addMethod([self class], sel,(IMP)setPropertyDynamic, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
// main函數(shù)中調(diào)用
Person *p = [[Person alloc]init] ;
p.weight = 100;
上面的例子捂掰,會打印出log:This is a dynamic method added for Person instance
2.重定向
再來通過demo理解重定向敢会,Person類和上面一樣,再建一個People類
//Person.m
// Person類重寫重定向方法这嚣,將weight的getter方法重定向到People類鸥昏,其他方法不處理
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(weight)) {
People *people = [People new];
return people;
}
id target = [super forwardingTargetForSelector:aSelector];
return target;
}
//People.h
@interface People : NSObject
@end
//People.m
@implementation People
- (NSInteger)weight {
return 666;
}
@end
// main函數(shù)中調(diào)用
Person *person = [[Person alloc] init] ;
NSInteger weightValue = person.weight;
NSLog(@"%ld",weightValue); //666
上面的例子,會打印出log:666
3.消息轉(zhuǎn)發(fā)
再來通過demo理解消息轉(zhuǎn)發(fā)姐帚,Person類吏垮、People類和上面一樣
//Person.h
//在.m文件中添加了@dynamic,所以下面的屬性不會自動添加setter和getter方法
@property(copy,nonatomic)NSString *identifier;
//Person.m
@dynamic identifier;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(setIdentifier:) || aSelector == @selector(identifier) ) {
NSMethodSignature *sign = [People instanceMethodSignatureForSelector:aSelector];
return sign;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
People *people = [[People alloc]init];
if ([people respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:people];
}else{
[super forwardInvocation:anInvocation];
}
}
//People.h
@property(copy,nonatomic)NSString *identifier;
// main函數(shù)中調(diào)用
Person *person = [[Person alloc] init];
person.identifier = @"yzk whlpkk";
NSLog(@"%@",person.identifier); //null
上面的例子罐旗,會打印出log:(null)
惫皱,之所以會這樣,是因為我們消息轉(zhuǎn)發(fā)的時候尤莺,每次都實例化了一個新的people旅敷,所有我們setter的people和getter的people并不是同一個實例。
再來說一下消息轉(zhuǎn)發(fā)和重定向的區(qū)別颤霎。
- 重定向只能重定向到一個對象媳谁,但是消息轉(zhuǎn)發(fā),可以同時對多個對象轉(zhuǎn)發(fā)友酱,只需要
[anInvocation invokeWithTarget:]
多個target
即可晴音。 - 重定向的target必須要有一個,如果是nil缔杉,則target就是當(dāng)前實例锤躁。消息轉(zhuǎn)發(fā)
可以不轉(zhuǎn)發(fā),即不調(diào)用[anInvocation invokeWithTarget:]
或详,不會crash系羞,但是消息轉(zhuǎn)發(fā)的methodSignatureForSelector:
方法簽名不能返回nil
,否則會crash霸琴。
最后附上demo