开发者聚焦:将图形计算器从 C++ 移植到 Swift
“开发者聚焦”系列旨在重点介绍来自世界各地有趣的 Swift 开发者。这篇文章由 Ron Avitzur 撰写,他是 Pacific Tech 图形计算器 的作者。
图形计算器始于 1985 年,使用 C 语言为 128K Macintosh 开发,那时还是 16 位整数、黑白 Quickdraw 和 8 MHz 68000 CPU 的时代,没有 MMU、FPU 和 GPU。那是一个更简单的时代。从那时起,发生了很多变化。
我一直秉持“如果没坏,就不要修”的理念,因此代码保留了许多过去的痕迹——当时有意义的设计选择,但现在已不再适用。它经历了 CPU 从 Motorola 68K 到 IBM PowerPC 系列,再到 Intel 和 ARM 的变化。它最初是为经典的 Inside Macintosh Mac API 编写的,然后是 Carbon,再到 Cocoa、AppKit 和 UIKit,现在是 SwiftUI。
编写新代码添加新功能并隐藏抽象层下的旧有代码更容易。最终,数十年积累的技术债务使新开发变得困难重重。图形计算器仍然使用 Classic Mac OS 9 协作线程 API,以便运行冻结在 20 世纪 80 年代的非线程安全代码。从头开始重写一切,也就是“从轨道上摧毁整个站点”,几乎永远不是一个好主意。遗留代码体现了当前开发人员从未经历过的数十年艰难学习的教训,即使是最初的开发人员(如果他们还在世),也早已忘记了。虽然全新的开始在美学上令人满意,但它会产生巨大的漏洞暴露面。在典型的点版本中,将测试重点放在新功能上很容易。在完全重写的情况下,一切都是新的。尽管如此,在将问题掩盖了 35 年之后,我决定最好的前进方向是审查一切并从头开始重写。
C++ 过去和现在都是管理大型项目复杂性的有效语言,那么我为什么要更改语言呢?我对 Apple 的增强现实技术印象深刻。在为我们的 iOS 产品添加 AR 支持后,我构建了一个原型应用程序,探索如何在数学教育中使用 AR,灵感来自儿童故事书中 AR 的使用。您可以在 http://PacificT.com/AR/ 和 https://twitter.com/RonAvitzur/status/1250520615993270272 上观看视频。该应用程序主要由 C++ 和 ObjectiveC++ 编写。原型使用了 ARKit 进行视觉和机器学习,虽然在 Objective-C 中是可行的,但在 Swift 中会更容易。很明显,对于 Apple 的新技术来说,情况仍然如此。
我通过移植图形计算器的核心计算机代数系统来学习 Swift。它最初是一个学习练习,然后变成了一个可行性研究。疫情在这一决定中发挥了作用,因为这成为了我的疫情居家隔离项目。重构可以用 C++ 和 Objective-C++ 完成,但不会那么有效,也不会那么有趣。这次移植混合了许多转变
从 | 到 |
---|---|
C++/ObjC/ObjC++ | Swift |
Lex/YACC | Swift |
pthreads | Swift 结构化并发 |
C++ char | Swift String |
AppKit/UIKit | SwiftUI |
OpenGL | SceneKit & Metal |
它还涉及重构和重写核心算法,这些算法通过其功能的逐步演变变得笨拙。
在过去的 18 个月 里,我一直在重写所有内容。以下是我所学到的。
我喜欢 Swift 的语法。Swift 消除了 C++ 中必要的许多重复的样板代码,只留下表示逻辑所需的代码,从而使含义更清晰。Swift 在集合类中使用值类型使对它们的推理更简单;语法糖使使用它们变得非常容易,并且它们仍然由使用自动引用计数和写时复制的实现支持,使其在几乎所有用途中都具有高性能。(发现该声明的局限性仍然是优化图形计算器性能的一个重要问题。)使用 Swift String 及其内置的 Unicode 支持取代了 C++ char、UTF-8 和 UTF-16 表示的混乱局面,改进了代码组织并使代码推理更容易。ARC、类型推断、可选类型、闭包、具有关联值的枚举、无需头文件以及 Swift Concurrency 都为编写简洁、富有表现力的代码做出了重大贡献。
最终,这次移植在可维护性、可读性和紧凑性方面都大大提高。当我移植各个功能部分时,Swift 源代码的大小通常是相应 C++ 代码的 30%。(虽然代码行数不是一个非常有用的指标,但它是一件容易衡量的事情。)更少的代码意味着更少的调试,更少的阅读和理解,仅此一项就使移植更容易维护。使用 SwiftUI,视图控制器完全消失了:声明式编程相对于命令式编程的一大胜利。总而言之,源代码从 152,000 行减少到 29,000 行,而功能或性能没有明显损失。
这次移植的最大挑战是实现相当的速度。在每个版本上进行数十年的迭代改进和底层优化为性能设定了很高的标准。在性能关键代码中导航 Swift 众多的 Unsafe API 很困难,但有效。最大的剩余 挑战 是在导航表达式树时最大限度地减少 ARC 保留/释放开销。依赖 ARC 消除了大量的代码复杂性。C++ 代码手动处理表达式内存管理,这既非常脆弱,但也非常快。Swift 版本更小,更容易编写正确的代码和进行推理,但在性能关键部分,我知道遍历树不会更改任何引用计数,但无法向编译器传达 ARC 保留/释放开销是不必要的。Swift 语言、库和运行时具有出色的文档,甚至可以在紧要关头检查开源实现。相比之下,SwiftUI 框架是闭源的。当 SwiftUI 工作时,它是一种近乎神奇的乐趣,但当它的行为出乎意料或需要偏离规定路径的行为时,可能很难理解和解决其局限性。
将代码移植到 Swift 值得我花费时间吗?我喜欢学习 Swift,并且现在对代码的状态更加满意。用 Swift 编写代码是一种纯粹的乐趣。自 80 年代以来,我一直打算最终开源我的代码。当我考虑对 C++ 代码库执行此操作时,我意识到由于数十年积累的技术债务使 C++ 代码无法维护,因此这不会是一个有用的贡献。我现在确信,新代码可以制成有用的独立 Swift 包,用于数学排版、编辑、数值和符号计算以及绘图。
Swift 兑现了其实现安全、快速和富有表现力的代码的承诺。SwiftUI 兑现了其以最少的代码在 Apple 平台上实现出色用户体验的承诺。我要感谢所有为 Swift 做出贡献的人们的辛勤工作。用 Swift 编程真的更有趣。特别感谢所有花时间在 Swift 论坛和 Twitter 上回答新手问题的人。对于您在此过程中的耐心和专业帮助,我感激不尽。