方法調用過程分析流程圖
文字描述:
1、從當前類反向遍歷繼承樹,直到NSObject,如果沒有找到了,就直接執行,到此調用流程結束,這是正常流程
2、當繼承樹中沒有找到方法實現,進入非正常調用流程(重定向和消息轉發)
3、此時可以重寫決議方法,在決議方法中動態添加方法實現,一旦添加成功,系統自動跳轉執行動態添加的方法實現,到此調用流程結束
4、如果沒有重寫決議方法或者方法中沒有動態添加方法方法實現,首先嘗試進入重定向流程(重定向流程標志重定向方法,且返回不是self或者nil)+ (BOOL)resolveInstanceMethod:(SEL)sel
和+ (BOOL)resolveClassMethod:(SEL)sel
。
5、如果重定重定向流程(重定向方法重寫的時候也可以動態添加方法實現,只不過不會自動執行,需要手動調用),且將調用重定向到另一個對象,該對象會調用自己的同名方法,流程同上,到此調用流程結束
- (id)forwardingTargetForSelector:(SEL)aSelector
6、如果沒有進入重定向流程,就會自動進入到消息轉發流程
7、消息轉發流程首先必須要執行的是方法簽名- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
8、執行完方法簽名,最終執行消息轉發forwardInvocation:(NSInvocation *)anInvocation
,在此方法中可以將任意修改方法,指定任意對象,執行任意可執行方法
9、如果繼承樹遍歷,動態決議,重定向和消息轉發都沒有找到合適的方法執行拋出異常unrecognized selector sent to instance
一、動態添加方法實現
注意:
1、動態添加方法實現可以在上述的決議方法(方法返回自動進入執行),重定向方法和轉發方法任意位置添加,推薦在決議方法中添加,流程越少越好,減少了代碼執行,和未知邏輯判斷,相對而言效率可定更高。
2、動態添加方法實現的區別主要在于
class_addMethod([self class], sel, testImp, "v@:");
和
class_addMethod(object_getClass(self), sel, testImp, "v@:");
添加實例方法使用[self class]
,添加類方法使用object_getClass(self)
3、類方法只能通過動態添加方法實現的方式處理,消息相關的都是對象,不是類
//動態添加實例方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@",NSStringFromSelector(sel));
id testImpBlock = ^(id self)
{
NSLog(@"對象方法->%@缺少實現",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod([self class], sel, testImp, "v@:");
return NO;
}
//動態添加類方法實現
+ (BOOL)resolveClassMethod:(SEL)sel
{
id testImpBlock = ^(id self)
{
NSLog(@"類方法->%@缺少實現",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod(object_getClass(self), sel, testImp, "v@:");
return NO;
}
二、重定向
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return [[Student alloc] init];
}
三、消息轉發
注意:
1、方法簽名和消息轉法必須是成對出現的
2、NSInvocation
和performSelector:withObject
OC發送消息的兩個方式,NSInvocation
對象中包含了方法執行的對象,要執行的方法及其參數,返回值等信息,NSInvocation
對象直接執行invoke
或者invokeWithTarget:(id)target
就是發送消息,執行方法。eg:如果在重定向中或者方法簽名中添加方法實現,在消息轉發中,直接用[anInvocation invoke]
就可以完成方法調用,但是不推薦,還是推薦如果使用動態添加方法實現放在決議方法中
//方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
//消息轉發
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSInteger count = anInvocation.methodSignature.numberOfArguments;
NSLog(@"%@->有%zd個參數",NSStringFromSelector(anInvocation.selector),count);
for (NSInteger i = 0; i < count; i++)
{
if (i > 1)
{
void *arg;
[anInvocation getArgument:&arg atIndex:i];
NSLog(@"%@",(__bridge id)arg);
}
}
// [anInvocation invoke];
}
四、應用 (避免出現unrecognized selector sent to instance異常)
1、直接實現決議方法,動態添加一個空的方法實現,那么永遠不會出現次異常,推薦使用,代碼如下即可:
//動態添加實例方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"%@",NSStringFromSelector(sel));
id testImpBlock = ^(id self)
{
NSLog(@"對象方法->%@缺少實現",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod([self class], sel, testImp, "v@:");
return NO;
}
//動態添加類方法實現
+ (BOOL)resolveClassMethod:(SEL)sel
{
id testImpBlock = ^(id self)
{
NSLog(@"類方法->%@缺少實現",NSStringFromSelector(sel));
};
IMP testImp = imp_implementationWithBlock(testImpBlock);
class_addMethod(object_getClass(self), sel, testImp, "v@:");
return NO;
}
2、對于對象方法,重寫消息轉發的兩個方法:
//方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
//消息轉發
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
}
3、功能宏
#define NOCRASH \
\
+ (BOOL)resolveInstanceMethod:(SEL)sel\
{\
id testImpBlock = ^(id self)\
{\
NSLog(@"對象方法->%@缺少實現",NSStringFromSelector(sel));\
};\
IMP testImp = imp_implementationWithBlock(testImpBlock);\
class_addMethod([self class], sel, testImp, "v@:");\
return NO;\
}\
\
+ (BOOL)resolveClassMethod:(SEL)sel\
{\
id testImpBlock = ^(id self)\
{\
NSLog(@"類方法->%@缺少實現",NSStringFromSelector(sel));\
};\
IMP testImp = imp_implementationWithBlock(testImpBlock);\
\
class_addMethod(object_getClass(self), sel, testImp, "v@:");\
\
return NO;\
}\
五、Block
1、block類似于函數指針,但是有時內聯的(代碼直接插入到調用者處,免去了普通函數調用的過程,效率更高)
2、block是一個代碼片段,在OC中被看作是OC對象
3、block建議使用copy策略
4、block分為三種__NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
,MRC默認情況下是__NSGlobalBlock__
,使用(捕獲)了外部非static和全局的變量會變成__NSStackBlock__
,__NSStackBlock__
的block使用了copy
策略,就變成__NSMallocBlock__
的了,ARC情況下copy和strong都一樣,系統默認會調用copy變成__NSMallocBlock__
5、在MRC和ARC下都不要使用assign
策略
為什么block使用copy
1、因為MRC下不使用copy的話,如果使用了__NSStackBlock__
的block,可能會出野指針
;
2、因為MRC下不使用copy的話,如果使用了__NSStackBlock__
的block,會出現線程不安全的隱患
;