Object-C消息转发

现在在OC代码里,用 performSelector: 这系列的方法,都会产生一个警告,告诉我们无法识别将要调用的方法,但这只是警告,还不是错误,仍然能运行起来,而且还运行的好好的。

OC是一门动态语言或运行时语言,这就是一个很好的特征,因为像 performSelector: 的参数指定的方法,只有在运行的时候才会去知道是哪个方法。

不论是 [objc sendMessage] 还是 [objc performSelector:@selector(sendMessage)];到底都是通过调用一个C的API来实现方法的调用的:objc_msgSend(id self, SEL op, ...) ,这个方法的第一个参数是消息的接收者,对应上面的objc,第二个是selector,对应@selector(sendMessage),剩余的参数是方法中所需要的参数,顺序要保持不变。

之后会进入到接收者的类中,从它的方法列表里看看有没有这个方法存在,如果没有,则根据接收者的继承体系回溯查找,最终还是没有的话,在抛出unrecognized selector sent to xxx崩溃前,还会走下面这三个方法,这三个方法也是三次挽回的机会。

  1. resolveInstanceMethod 和 resolveClassMethod

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

这两个方法的意义其实是一样的,区别只是一个处理实例方法,一个处理类方法,作用都是动态添加一个方法来处理sel。这个方案可以用来实现@dynamic属性。

@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

赋值和读取的代码

    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.


转载于:https://my.oschina.net/u/574245/blog/528869

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值