KAOLA's note.

消息转发

字数统计: 765阅读时长: 3 min
2018/11/26 Share

Background or Question

  1. 一般给对象的属性赋值主要有两种方式, _var=newValueobjc.var = newValue,大家都知道前者效率高,后者效率低
  2. 开发的过程中,引起APP奔溃的叫常见的一个场景是unrecognizedSelector,顾名思义是无法识别的方法or选择子
  3. 经常会有人问一个很宏观的问题:你是如何理解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];
// Do any additional setup after loading the view, typically from a nib.
}
- (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
        • forwardInvocation
① 在分类中重写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版本中尽量覆盖到所有动态确定的方法上

我的困惑

forwardingTargetforwardInvocation在本来找不到对应的方法时,可以将消息转发给可以其他对象,那他们的区别是什么?

CATALOG
  1. 1. Background or Question
  2. 2. 一些解释
    1. 2.1. property = getter+setter+ivar
    2. 2.2. unrecognizedSelector
      1. 2.2.1. 场景
      2. 2.2.2. 发生的原因
        1. 2.2.2.1. runtime查找方法的流程
        2. 2.2.2.2. 消息转发的流程
          1. 2.2.2.2.1. ① 在分类中重写resolveInstanceMethod 动态添加方法
          2. 2.2.2.2.2. ② forwardingTargetForSelector 转发
          3. 2.2.2.2.3. ③ forwardInvocation 转发
  3. 3. 我们应该怎么做
  4. 4. 我的困惑