Objective-C Runtime

背景

Runtime 是 Objective-C 特有的机制,iOS 进阶必须要掌握的知识点,面试过程中也会经常问。实际上也有很多开源库中,大量地使用 Runtime 来实现各种需求,比如大名鼎鼎的 JSPatch,YYModel 等。本文是我学习的笔记或随笔,内容可能比较散杂,由于 OC Runtime 内容很多,所以本文会不间断更新。先上一张思维导图,感受一下 OC Runtime 大体包含了哪些知识点。

消息发送

消息发送是OC作为动态类型语言的一个核心特性,OC方法调用并不是编译期间就确定的,而且是运行期间动态地决定调用哪个方法。看一个OC消息发送的例子:初始化了一个Person实例,然后直接调用Person类的eat方法。

1
2
Person *p = [[Person alloc] init];
[p eat];

再看一下对应的底层实现:

1
objc_msgSend(p, @selector(eat));

主要流程:

  • 查找selector的实现IMP
  • 消息发送 objc_msgSend

本质上OC方法调用是通过objc_msgSend给target发送消息,如果这个消息没有对应的实现,就回进入消息转发流程。

消息转发

在发消息的时候,如果 selector 有对应的 IMP ,则直接执行;如果没有,这时就要进入到消息转发流程。依次有 resolveInstanceMethod 、forwardingTargetForSelector、forwardInvocation,如果消息转发流程中3个步骤任然没有找到对应的selector的IMP,就会抛出 ** unrecognized selector sent to instance 这样的异常,表明你曾向某个对象发送了一条无法解读的消息。整体流程如下图:

动态方法调用

实际项目开发时,或多或少都会需要用到动态方法调用。我在项目开发中有两个场景使用了动态方法调用:

  • 想使用某个基础库的功能,但是改库并没有暴露头文件
  • 使用某个基础库,但是不希望在编译期间引入对这个库的依赖

动态方法调用的适用场景还有更多,比如我们想要使用系统的私有方法,也可以通过动态方法调用的机制。不过这样有审核风险,所以一般不推荐使用。

实例方法调用流程

代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Class Person =NSClassFromString(@"Person");
if (Person) {
id person = [[Person alloc] init];
if (person == nil) {
return;
}
NSMethodSignature *eat = [person methodSignatureForSelector:NSSelectorFromString(@"eat:")];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:eat];
int cnt = 10;
[invocation setArgument:&(cnt) atIndex:2];
invocation.selector = NSSelectorFromString(@"eat:")
invocation.target = person;
[invocation invoke];
}

流程总结:

  • 获取 Class 定义 (NSClassFromString)
  • 实例化 Class
  • 定义实例方法签名 (methodSignatureForSelector)
  • 创建 NSInvocation (invocationWithMethodSignature)
  • 设置 invocation 参数 (setArgument,index从2开始)
  • 获取待调用方法 selector (NSSelectorFromString)
  • 设置 invocation 的 selector
  • 设置 invocation 的 target (Class 实例)

修改历史

修改时间 说明
2018-06-25 创建文档
2018-06-26 增加思维导图
2018-06-27 增加消息转发部分

更多资料