1. 程式人生 > >Objc_sendMsg訊息傳遞

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, "

[email protected]:")。

方法二: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:類例項變數大小