Background or Question
一般给对象的属性赋值主要有两种方式, _var=newValue 和 objc.var = newValue,大家都知道前者效率高,后者效率低
开发的过程中,引起APP奔溃的叫常见的一个场景是unrecognizedSelector,顾名思义是无法识别的方法or选择子
经常会有人问一个很宏观的问题:你是如何理解runtime的,我觉得runtime 有两个角度可以解释
一套用c实现的底层API 源码可以在apple的source library里下载
一种动态运行机制
在iOS中,程序运行时调用的方法【or 消息】并不是编译时期就确定下来的,可以在运行时才确定、甚至是修改。 消息转发的机制,也是因为有runtime,才能如此设计。
一些解释 property = getter+setter+ivar * 声明一个属性后,编译器会自动生成getter、setter方法和实例变量`_ivar`
* 当我们调用`objc.var = newValue`时,runtime会查找`setterObjc`,然后更新`_ivar`
* 而`objc.var = newValue`则是直接更新`_ivar`
* 从效率上看,调用`objc.var = newValue`需要查找`setterObjc`,即使是有methodCache,仍然多了一层消息发送
* 从具体应用来看,`objc.var = newValue`无法触发KVO的valueWillChange、valueDidChange这些方法
* 在实际应用中,一味地追求效率的极致并不可取,具体问题具体分析。
unrecognizedSelector场景 1 2 3 4 5 6 7 8 9 10 11 12 @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; [self sendMsg]; } - (void )sendMsg { [self performSelector:@selector (ghostfunction)]; } @end
* ViewController这个类并没有实现`ghostfunction`
* 这个场景很熟悉,如果没有预防措施的话,会抛出`***unrecognized selector sent to instance XXX***`
发生的原因 runtime查找方法的流程 <!-- 这里插入方法查找的流程 -->
* objc_msgSend or 其他
* 判断receiver,nil则return
* 在缓存中查找
* 在class的methodlist中寻找
* 在父类的methodlist中查找,并依次往上寻找直到找到为止
* 找到方法之后,填充到缓存中,并返回selector
* 最终没有找到,调用_classs_resolveMethod
* 毫无办法,则抛出异常
消息转发的流程
resolveInstanceMethod
forwardingTargetForSelector
methodSignatureForSelector
① 在分类中重写resolveInstanceMethod 动态添加方法 1 2 3 4 5 6 7 8 9 10 11 12 13 + (BOOL )resolveInstanceMethod:(SEL)sel { NSString *clsStr = NSStringFromSelector (sel); if ([clsStr isEqualToString:@"hello" ]) { class_addMethod([self class ], sel, (IMP)mymethod, "v@:@" ); return YES ; } return [super resolveInstanceMethod:sel]; } void mymethod(id self , SEL _cmd){ NSLog (@"hello method added" ); }
② forwardingTargetForSelector 转发 1 2 3 4 5 6 7 8 - (id )forwardingTargetForSelector:(SEL)aSelector { NSString *clsStr = NSStringFromSelector (aSelector); if ([clsStr isEqualToString:@"hello" ]) { return [TestObj new]; } return nil ; }
在TestObj中实现了:1 2 3 4 - (void )hello { NSLog (@"found hello" ); }
`forwardInvocation`的调用栈:
* `forwardInvocation`
* `doesNotRecognizeSelector`
* `_objc_fatal`
③ forwardInvocation 转发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (void )forwardInvocation:(NSInvocation *)anInvocation { NSString *clsStr = NSStringFromSelector (anInvocation.selector); if ([clsStr isEqualToString:@"hello" ]) { [anInvocation invokeWithTarget:[TestObj new]]; } } -(NSMethodSignature *)methodSignatureForSelector:(SEL)selector { NSMethodSignature *signature = [super methodSignatureForSelector:selector]; if (! signature) { signature = [[TestObj new] methodSignatureForSelector:selector]; } return signature; }
跟②相似,③也是将消息转发给备用接收者。
我们应该怎么做
消息备援在release版本中尽量覆盖到所有动态确定的方法上
我的困惑 forwardingTarget和forwardInvocation在本来找不到对应的方法时,可以将消息转发给可以其他对象,那他们的区别是什么?