在 Instagram 和 Facebook,React Native 让我们能够以更快的速度推进迭代产品。在模拟器上使用⌘R 快捷键就可以查看更改效果。然而使用原生代码进行开发,每次都需要重新编译、链接、安装、启动等一系列步骤才能查看更改效果。
即便是一个小项目,这样的开发流程也会让开发者感到很繁琐。在我们的项目规模上,结果就是工程师要花费大量的时间来等待电脑完成构建任务,等待的过程中又经常会切换到其他应用,从而浪费了更多的时间。
去年年底我们着手将 React Native 的开发体验带入 Objective-C 原生代码的开发中,目的是让工程师能及时看到代码更改效果。为了实现这一点,我们通过在运行时加载新代码来避免重启应用。
如何重新加载原生代码
为了重新加载原生代码,第一步需要将修改过的代码转换成正在运行的应用可以加载的动态库。这一步操作非常快,该操作以文件为单位进行:只编译有改动的文件,并链接为一个动态库。
iOS 真机设备不允许应用加载其自身 bundle 以外的动态库。同时也不允许应用向其自身 bundle 写文件。因此可加载的动态库必须在应用安装时就已经存在其 bundle 里,因此无法在真机上加载新的动态库。
幸运的是,iOS 模拟器上并没有这些限制。每个模拟器的文件系统其实就是 Mac 文件系统里的一个文件夹。另外,iOS 模拟器中的 dlopen 函数不会按文件位置进行过滤,这使得无论如何都可以直接将动态库写入应用程序的 bundle 中。
我们在程序代码中新增了一个启动方法,该方法会在 debug 模式下生成一个后台任务来监听指定文件夹的内容变化。动态库创建好后会移动到指定文件夹。这会触发应用中设置好的回调,执行以下几个步骤:
- 加载新的动态库。
- 确定添加了哪些 Objective-C 类。
- 查找这些类的原始版本。Objective-C 类本身也是对象,很容易通过名字找到。
- 使用 Objective-C 运行时 APIs 来替换这些原始类的方法实现为新的实现。这会替换类所有的方法实现,因为我们无法确定哪些方法实现有改动。
- 将执行结果写回相同目录以便工具读取。
到这里我们的新代码已经在应用中跑起来了,下一次调用这些方法就会执行我们的新代码。当然,这只有在 iOS 模拟器上做开发时可用,真机和生产环境是不可行的。
构建开发体验
重新安装、重启应用来查看更改效果很慢,但简单可靠。在 Xcode 中按下按钮或快捷键,最终,代码的结果会显示在屏幕上。我们在我们的插件工具中做了类似的事情:在 Xcode 的工具栏放置了一个按钮和快捷键来触发加载新的代码。
与重启应用相比我们还有另一个优势:保持应用的当前状态。重启会重置应用回到最初的入口 - 对我们的应用来说是 feeds 首页。负责其他模块的工程师必须重新点击进入到自己的模块才能查看他们的修改是否正确。
然而,新的代码加载后,什么也不会发生:应用之前的状态不会受到影响。幸运的是,在 Facebook 上使用的声明式 iOS UI 框架 ComponentKit 具有执行即时重新布局的有用方法: + [CKComponentDebugController reflowComponents] 。使用 ComponentKit 构建的视图可以在新代码加载后调用该方法,来让布局代码重新执行并,并在屏幕上立即更新。结果就是,使用 ComponentKit 进行布局,工程师可以及时查看布局的修改。以前,工程师需要批量更新并考虑何时需要重启。 现在他们可以交互式地迭代工作。
总结
我们的工具在小组内已变得非常流行,特别是使用 ComponentKit 的工程师。在大型应用中,这使得工程师查看修改结果的速度提升了 20 倍。高性能、与现有开发工具和框架的紧密结合已经让原生代码的加载成了在 Instagram 和 Facebook 的 iOS 工程师平时工作流程中的重要一环。
Nate Stedman 一名来自 Instagram 纽约的 iOS 工程师。
感谢覃云对本文的审校。
评论