Objc_sendMsg訊息傳遞
在IOS開發中我們會經常碰到類似下面這樣的錯誤:
分析其原因:大概是因為呼叫了某個類沒有實現的方法,導致程式報錯。然後,物件的方法走向是先去自身裡面查詢,如果有直接被調起,如果沒有則去父類的方法中查詢,沿著繼承鏈一直往下查詢,沒找到就進入下面的訊息轉發流程了。
然而深入去分析一波,則要牽扯到IOS的訊息轉發機制了。
下面簡單的分析一下IOS中訊息轉發機制的流程走向,這裡有一張圖可以很清晰的介紹具體走向:
方法一:resolveInstanceMethod
// -------轉發流程------------
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod:%@",NSStringFromSelector(sel));
if (sel == @selector(log1) ) {
class_addMethod([self class], sel, (IMP)log1, " [email protected]:");//此方法未實現時,走到下一步
return YES;
}
BOOL canResolve = [super resolveInstanceMethod:sel];
return canResolve;
}
resolveInstanceMethod方法會檢測該物件內部是否實現了被呼叫方法,如果沒有實現則直接返回NO,則會走到下一步-forwardingTargetForSelector方法中;否則,需要我們在方法體中動態地新增方法,如:class_addMethod([self class], sel, (IMP)log1, "
方法二:forwardingTargetForSelector
-(id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"forwardingTargetForSelector:%@",NSStringFromSelector(aSelector)); id student = [NSClassFromString(@"Student") new]; if ([student respondsToSelector:aSelector]) { return student; } return [super forwardingTargetForSelector:aSelector]; }
forwardingTargetForSelector方法會檢測有沒有新目標能夠響應被呼叫方法,在這裡我們可以提供一個新的目標物件使其來響應這個方法。如果轉發成功,則直接去Student物件裡面呼叫這個Selector了,而不會繼續報錯。否則,轉發流程會進到下一步forwardInvocation。
方法三:forwardInvocation
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"我是轉發方法, 你可以在這裡做方法處理:%@",NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(jump)) {
id student = [NSClassFromString(@"Student") new];
[anInvocation invokeWithTarget:student];
}
}
在這個方法中,我們可以採用NSInvocation的方式去呼叫物件的方法。然而,在呼叫這個方法之前需要提供一個方法簽名methodSignatureForSelector。
方法四:methodSignatureForSelector
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector:%@",NSStringFromSelector(aSelector));
if (aSelector == @selector(jump)) {
return [NSMethodSignature signatureWithObjCTypes:"[email protected]:@"];
}
return [super methodSignatureForSelector:aSelector];
}
此方法提供一個方法簽名,以便在方法三種獲取到方法名,進入轉發流程後期,如果這裡沒有返回一個NSMethodSignature,那麼程式會直接走到doesNotRecognizeSelector,然後交給系統丟擲錯誤。
方法五:doesNotRecognizeSelector
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"doesNotRecognizeSelector:%@",NSStringFromSelector(aSelector));
[super doesNotRecognizeSelector:aSelector];
}
當程式走到這一步時,就代表了前面三部挽救方式都沒有通過,就給系統丟擲異常。
下面給一個到第三步挽救的結果:
// -------轉發流程------------
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod:%@",NSStringFromSelector(sel));
if (sel == @selector(log1) ) {
//class_addMethod([self class], sel, (IMP)log1, "[email protected]:");//此方法未實現時,走到下一步
//return YES;
}
BOOL canResolve = [super resolveInstanceMethod:sel];
return canResolve;
}
-(id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"forwardingTargetForSelector:%@",NSStringFromSelector(aSelector));
id student = [NSClassFromString(@"Student") new];
if ([student respondsToSelector:aSelector]) {
//return student;
}
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector:%@",NSStringFromSelector(aSelector));
if (aSelector == @selector(jump)) {
return [NSMethodSignature signatureWithObjCTypes:"[email protected]:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"doesNotRecognizeSelector:%@",NSStringFromSelector(aSelector));
[super doesNotRecognizeSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"-forwardInvocation-你可以在這裡做方法處理:%@",NSStringFromSelector(anInvocation.selector));
if (anInvocation.selector == @selector(jump)) {
id student = [NSClassFromString(@"Student") new];
[anInvocation invokeWithTarget:student];
}
}
正常結果:
到此,轉發流程就差不多結束了。
這裡補充一個類的靜態方法(工廠方法)的訊息轉發補救措施。
// ----------類的工程方法----------
void superLog(){
NSLog(@"superLog");
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod:%@",NSStringFromSelector(sel));
if (sel == @selector(superLog)) {
class_addMethod([self superclass], sel, (IMP)superLog, "[email protected]:");
}
return [super resolveClassMethod:sel];
}
// ----------類的工程方法----------
方法的實現類似於物件的例項方法轉發的第一步,這裡是通過執行時動態的把方法新增的父類的集合中,這裡為什麼要新增到父類方法中,後面有機會講到類的結構體分析時可以重點介紹一波,這裡簡單分析下:
isa: 指向元類的objc_class結構體指標,iOS中的類也是物件,元類中儲存有類物件的類方法;
superclass: 指向父類的objc_class結構體指標,可以通過父類的指標找到變數和方法;
name: 類名;
version: 版本號,預設為0
info: 其他資訊,執行期間的一些位標示
instance_size:類例項變數大小