相信接触过 iOS 的同学对 runtime 或多或少都有耳闻. 创建一个对象:
[[NSObject alloc] init];
点击进入其定义:
可以发现其进入了文件 NSObject.h 中
右击该文件, 选择: show in Finder 可以看到 runtime 暴露给我们的文件:
虽然暴露给我们的不多, 但其实已经能提供给我们很多功能, 刚刚我们的对象创建就是一个典型的功能. 众所周知, NSObject 对象是 Objective-C 语言中几乎所有对象的根类. 换言之, 任何一个对象的创建都是通过 runtime 实现的, 仅此一点, runtime 的重要性可见一斑, 现在的 iOS 面试也越来越通过对 runtime 的面试来区分 iOS 开发人员的水平高低.
笔者研究 runtime 源码有一段时间了, 随着研究的深入, 对 runtime 的实现也越来越感兴趣, 因此想写一套系列教程来和大家讨论 runtime 的底层实现.
本文完整版详见笔者小专栏: https://xiaozhuanlan.com/runtime
下载源码
苹果开源了 runtime 的实现, 在网站 https://opensource.apple.com/source/objc4/ 中可以找到各个版本的 runtime 源码. 但提供的是一个个文件, 不方便打包下载, 网站 https://opensource.apple.com/tarballs/objc4/ 中提供了压缩包的下载.
编译
下载下来的 runtime 源码是运行不了的, 缺少一些依赖文件, 找起来也比较繁琐. 这里笔者 fork 了一份, 供大家参考 (该项目编译过程大家可以参考这篇文章: objc - 编译 Runtime 源码 objc4-680):
runtime 源码 706 https://github.com/zjh171/objc4-706
如图, 打开工程后选择工程 debug-objc, 点击 run 即可. 由于 debug-objc 依赖于 objc(即 runtime 的源代码编译的库), 因此我们在 main 函数中所有 Objective-C 的代码会调用我们编译的 runtime, 从而方便我们调试.
目录分析
runtime 源码目录结构如下:
include 文件夹是我们引入的项目需要的依赖文件
Public Headers 文件夹是对外暴露的, 点开后我们不难发现, 和文章开头给出的文件列表一模一样:
Private Headers 从字面意思了解, 是私有的一些方法
Project Headers runtime 项目中会用到的头文件
Obsolete Headers 一些孤立的文件, 大部分可删, 只有 hashtable2.h 的文件会被其他文件使用到.
Obsolete Source 无实质用处, 可全删
Source 目录, 是 runtime 的实现文件集合, 后面的文章主要是研究这个目录.
小试牛刀
在我们的 main.m 中, 输入以下代码:
- #import <Foundation/Foundation.h>
- int main(int argc, const char * argv[]) {
- @autoreleasepool {
- NSObject *obj = [[NSObject alloc] init];
- }
- return 0;
- }
打断点后, 我们会发现 NSObject *obj = [[NSObject alloc] init]; 方法最终会调用到 runtime 中的 NSObject.mm 中. 也就是说, 对象的创建都是在 NSObject.mm 中完成的. 具体实现流程, 会在后面的文章中逐步揭晓.
来源: https://juejin.im/post/5c1ef2446fb9a04a0821ab47