如今在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
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.