1. 程式人生 > >iOS消息轉發學習筆記

iOS消息轉發學習筆記

實現 異常 col color 開頭 創建 rec eat 繼承

如果深入學習ios Runtime,不得不提到消息轉發,很多框架的實現都基於這一功能實現(例如JSPatch)

雖然看了很多篇關於消息轉發的文章,但是理解的不是很透徹,還是自己實踐一些理解能更加透徹一下。

首先我自己定義了一個MyString繼承NSString

@interface MyString : NSString

@end

@implementation MyString

@end

然後創建一個MyString,通過performSelector調用MissMethod,MissMethod1,MissMethod2等方法。

- (void)testForward
{
    MyString 
*str = [[MyString alloc] init]; [str performSelector:@selector(MissMethod) withObject:nil]; [str performSelector:@selector(MissMethod2) withObject:nil]; [str performSelector:@selector(MissMethod3) withObject:nil]; }

如果什麽都不寫,這樣肯定會crash,會出現這個錯誤[NSObject(NSObject) doesNotRecognizeSelector:],因為MyString沒有這三個方法,而且父類也沒有。

如果沒有找到方法,系統會嘗試進行補救,看看有沒有能處理的能力,首先會調用resolveInstanceMethod這個方法,這個方法默認是返回NO,走一下步流程,如果返回YES,例如下面方法的實現,如果傳入的sel的名稱是MissMethod開頭,則認為我們的類是可以處理這個方法的。而且如果是MissMethod方法,我們就給這個類添加一個方法dynamicMethodIMP,作為MissMethod的實現。此時當外界調用MissMethod時,其實相當於調用dynamicMethodIMP

void dynamicMethodIMP(id self, SEL _cmd) {

NSLog(@" >> dynamicMethodIMP");

}

+ (BOOL)resolveInstanceMethod:(SEL)name
{
    NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));
    
    NSString *selName = NSStringFromSelector(name);
    
    if ([selName hasPrefix:@"MissMethod"]) {
        if (name == @selector(MissMethod)) {
            class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
            return YES;
        } else {
            return NO;
        }
    }
    
    return [super resolveInstanceMethod:name];
}

如果resolveInstanceMethod方法返回NO了,下面怎麽辦?首先我定義了一個類MyString2作為接盤俠,實現了MissMethod1,MissMethod2方法

@interface MyString2 : NSString

@end

@implementation MyString2

- (void)MissMethod2
{
    NSLog(@"MissMethod2");
}

- (void)MissMethod3
{
    NSLog(@"MissMethod3");
}

@end

當resolveInstanceMethod方法返回NO了,系統會嘗試調用下面方法,看看有沒有接盤俠來接這個鍋,_str2是MyString2的一個示例,而且實現了MissMethod2方法。當MyString的示例調用MissMethod2方法,MissMethod2->resolveInstanceMethod(NO)->forwardingTargetForSelector,返回一個接盤俠去給這個示例發送消息objc_sendmsg(_str2,sel)

- (id)forwardingTargetForSelector:(SEL)sel {
    if(sel == @selector(MissMethod2)){
        return _str2;
    }
    return [super forwardingTargetForSelector:sel];
}

當forwardingTargetForSelector也無法處理,返回nil時,下面會走到這個方法methodSignatureForSelector,判斷sig是否為nil,如果不為nil會走forwardInvocation,最後調用forwardInvocation,如果這個方法也沒有處理,做了最後嘗試之後也就會拋出那個異常doesNotRecognizeSelector

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    
    NSMethodSignature *sig;
    sig = [_str2 methodSignatureForSelector:sel];
    if (sig) {
        return sig;
    }
    return [super methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = nil;
    if ([_str2 methodSignatureForSelector:[invocation selector]] ) {
        target = _str2;
        [invocation invokeWithTarget:target];
    }
}

總結一下消息轉發的整個流程,大致流程MissMethod->resolveInstanceMethod(NO)->forwardingTargetForSelector(nil)->methodSignatureForSelector(sig)->forwardInvocation。

下面是完整代碼實現:

//
//  MyString.m
//  objc
//
//  Created by zilong.li on 2017/8/17.
//
//

#import "MyString.h"

#import <objc/runtime.h>

@interface MyString2 : NSString

@end

@implementation MyString2

- (void)MissMethod2
{
    NSLog(@"MissMethod2");
}

- (void)MissMethod3
{
    NSLog(@"MissMethod3");
}

@end

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@" >> dynamicMethodIMP");
}

@interface MyString ()
{
    MyString2 *_str2;
}

@end

@implementation MyString

- (instancetype)init
{
    self = [super init];
    if (self) {
        _str2 = [[MyString2 alloc] init];
    }
    return self;
}

+ (BOOL)resolveInstanceMethod:(SEL)name
{
    NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));
    
    NSString *selName = NSStringFromSelector(name);
    
    if ([selName hasPrefix:@"MissMethod"]) {
        if (name == @selector(MissMethod)) {
            class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:");
            return YES;
        } else {
            return NO;
        }
    }
    
    return [super resolveInstanceMethod:name];
}

- (id)forwardingTargetForSelector:(SEL)sel {
    if(sel == @selector(MissMethod2)){
        return _str2;
    }
    return [super forwardingTargetForSelector:sel];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    
    NSMethodSignature *sig;
    sig = [_str2 methodSignatureForSelector:sel];
    if (sig) {
        return sig;
    }
    return [super methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = nil;
    if ([_str2 methodSignatureForSelector:[invocation selector]] ) {
        target = _str2;
        [invocation invokeWithTarget:target];
    }
}

@end

iOS消息轉發學習筆記