Object-C消息轉發

如今在OC代碼裏,用 performSelector: 這系列的方法,都會產生一個警告,告訴咱們沒法識別將要調用的方法,但這只是警告,還不是錯誤,仍然能運行起來,並且還運行的好好的。數組

OC是一門動態語言或運行時語言,這就是一個很好的特徵,由於像 performSelector: 的參數指定的方法,只有在運行的時候纔會去知道是哪一個方法。函數

不管是 [objc sendMessage] 仍是 [objc performSelector:@selector(sendMessage)];到底都是經過調用一個C的API來實現方法的調用的:objc_msgSend(id self, SEL op, ...) ,這個方法的第一個參數是消息的接收者,對應上面的objc,第二個是selector,對應@selector(sendMessage),剩餘的參數是方法中所須要的參數,順序要保持不變。atom

以後會進入到接收者的類中,從它的方法列表裏看看有沒有這個方法存在,若是沒有,則根據接收者的繼承體系回溯查找,最終仍是沒有的話,在拋出unrecognized selector sent to xxx崩潰前,還會走下面這三個方法,這三個方法也是三次挽回的機會。spa

  1. resolveInstanceMethod 和 resolveClassMethod指針

+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel

這兩個方法的意義實際上是同樣的,區別只是一個處理實例方法,一個處理類方法,做用都是動態添加一個方法來處理sel。這個方案能夠用來實現@dynamic屬性。code

@interface MessObj : NSObject

@property (nonatomic, strong) NSString* myMsg;

@end
@interface MessObj ()

@property (nonatomic, strong) NSMutableDictionary* propertyDic;

@end

@implementation MessObj

@dynamic myMsg;

void dynamicPropertySetter(id self, SEL _cmd, id value) {
    
    NSString* seletorStr = NSStringFromSelector(_cmd);// 結果是setMyMsg:(有個冒號)
    NSMutableString* key = [seletorStr mutableCopy];
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];// setMyMsg(去了冒號)
    [key deleteCharactersInRange:NSMakeRange(0, 3)];// MyMsg
    
    NSString* lowerFirstChar = [[key substringToIndex:1] lowercaseString];
    
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowerFirstChar];// myMsg
    
    MessObj* typedSelf = (MessObj *)self;
    if (value)
    [typedSelf.propertyDic setObject:value forKey:key];
    
}

id dynamicPropertyGetter(id self, SEL _cmd) {
    
    NSString* key = NSStringFromSelector(_cmd);
    MessObj* typedSelf = (MessObj *)self;
    return [typedSelf.propertyDic objectForKey:key];
    
}

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

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString* seletroStr = NSStringFromSelector(sel);
    if ([seletroStr hasPrefix:@"set"]) {
        
        class_addMethod(self, sel, (IMP)dynamicPropertySetter, "v@:@");
        
    } else {
        
        class_addMethod(self, sel, (IMP)dynamicPropertyGetter, "@@:");
        
    }
    return YES;
}

@end

賦值和讀取的代碼orm

    MessObj* messObj = [[MessObj alloc] init];
    messObj.myMsg = @"hi";
    
    NSString* outStr = messObj.myMsg;
    NSLog(@"%@", outStr);

上面的幾個地方解釋一下:繼承

class_addMethod是添加新方法的函數,前兩個參數好理解,第三個參數是一個IMP指針, IMP指針是一個方法的實現指針,在文檔裏這樣的解釋,我曾嘗試直接建立一個SEL的方法,而後轉化爲IMP來成爲添加的新方法,結果程序不接受,查了下文檔,發現裏面有這個解釋:索引

A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.文檔

就是說IMP指針指向的方法要至少包含兩個參數,一個是id類型的self,一個是SEL的_cmd。猜想,應該都是相似objc_msgSend實現,而objc_msgSend至少須要兩個參數,做爲接受者的self,和指向的方法 _cmd。

最後一個參數是類型符號,是把一個方法符號化,"v@:@"的v表明的是的返回類型是void,第一個@是表明self的類型id,冒號:是表示方法_cmd,第二個@表示參數的類型是NSString。若是類型是浮點型,對應的符號是f。


(補充:1.對於類型符號,能夠用method_getTypeEncoding來獲取;

            2.能夠直接建立一個SEL方法,而後轉化爲IMP來成爲添加的新方法,可是須要作如下處理,緣由請查看class_addMethod在文檔裏的解釋

- (void)toSetMyMsg:(NSString *)value {
    
    //代碼和上面dynamicPropertySetter同樣
    
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString* seletroStr = NSStringFromSelector(sel);
    if ([seletroStr hasPrefix:@"set"]) {
        
        Method newMethod = class_getInstanceMethod(self, @selector(toSetMyMsg:));
        IMP newIMP = method_getImplementation(newMethod);
        
        if (!class_addMethod(self, sel, newIMP, "v@:@")) {
            
            Method origMethod = class_getInstanceMethod(self, sel);
            
            method_setImplementation(origMethod, newIMP);
        }
        
    } else {
        
        class_addMethod(self, sel, (IMP)dynamicPropertyGetter, "@@:");
        
    }
    return YES;
}


2.forwardingTargetForSelector

第二個機會是,若是自身及其繼承體系不實現這個方法,那麼指定一個備援接收者。咱們把上面的類給徹底的改一下:

@implementation MessObj

- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    NSMutableArray* arr = [NSMutableArray array];
    return arr;
    
}

@end

調用方法是這樣的:

MessObj* messObj = [[MessObj alloc] init];
[messObj performSelector:@selector(addObject:) withObject:@1];

MessObj類是沒有addObject的方法的,咱們指定了一個可變數組的類來做爲備援接受者。若是這個方法裏返回的是nil或是self,就會往下執行第三個方法。


3.methodSignatureForSelector和forwardInvocation

咱們仍是以動態屬性爲例

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    NSString *sel = NSStringFromSelector(aSelector);
    if ([sel rangeOfString:@"set"].location == 0)
    {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    else
    {
        return [NSMethodSignature signatureWithObjCTypes:"@@:"];
    }
    
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    NSString* seletorStr = NSStringFromSelector([anInvocation selector]);
    
    if ([seletorStr rangeOfString:@"set"].location == 0)
    {
        NSMutableString* key = [seletorStr mutableCopy];// 結果是setMyMsg:(有個冒號)
        [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];// setMyMsg(去了冒號)
        [key deleteCharactersInRange:NSMakeRange(0, 3)];// MyMsg
        
        NSString* lowerFirstChar = [[key substringToIndex:1] lowercaseString];
        
        [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowerFirstChar];
        
        NSString *obj;
        [anInvocation getArgument:&obj atIndex:2];
        [_propertyDic setObject:obj forKey:key];
    }
    else
    {
        NSString *obj = [_propertyDic objectForKey:seletorStr];
        [anInvocation setReturnValue:&obj];
    }
    
}

methodSignatureForSelector生成了一個方法前面給到forwardInvocation,以後的作法和resolveInstanceMethod基本是一直的,這裏解釋一下 [anInvocation getArgument:&obj atIndex:2] 裏面的2是指咱們傳進來的參數的索引,每一個方法的調用都是經過objc_msgSend,它的第一個參數是接收者,第二個是方法名,第三個纔是咱們的傳參,因此索引是2.

相關文章
相關標籤/搜索