四娘的窝

无论你是否主力 Swift, 这些事情你都可以了解一下

Swift 再等等?我的答案是:快上车 - 简书

主力 Swift 或许真的要等一等 - 知乎专栏

上一周有两篇文章, 分别讨论了大家在现阶段到底该不该用 Swift

在这里我不是想给出一个答案该不该用 Swift, 只是想聊一聊我对于 Swift 的理解.

Swift 能不能取代 Objective-C?

那两篇文章都讨论了一个话题, Swift 到底能不能取代 Objective-C?

其实到现在为止 Swift 离替代 Objective-C 还是很遥远,因为 Apple 内部一直在用 Objective-C 来做一些 Framework 的开发,低层也不可能用 Swift 实现

— Cyan

如果把取代定义为编写底层框架的话, 至少以 Swift 3 来说, 毫无疑问是不可能的.

首先我们先去探究一下为什么苹果会选择 Objective-C 作为官方开发语言?

Objective-C 是在 C 的基础上加上一层面向对象, 在编写高性能模块的时候, 可以直接使用 C, 在编写对于性能不高的业务模块时, 就使用原生的 Objective-C.

目前苹果的框架, 底层实现都是使用 C , 然后再套上一层 Objective-C 去暴露外部接口. 这种方式既保证了性能, 又能保证 API 的易用性.

而且苹果使用了大量的开源框架, 系统底层很多模块, 都已经有了开源的基于 C 语言的框架实现, 跟 C 的交互上, Swift 明显不如 Objective-C 那么方便.

除开语言生态, 还有一个问题是 Swift 的性能不如 C, Swift 在公布的时候, 就宣称运行效率可以媲美甚至超越 C. 但这是有前提的, 没有运行时损耗的情况下 Swift 才有可能与 C 的运行效率持平.

Swift 的运行时损耗到底来自哪里

Swift 的运行效率损耗主要来自于 ARC, 内存管理, 运行的时候, 一个对象会不停地进行 retain 和 release, runtime 必须一直 observe 每一个对象的 retain count, 当 retain count 达到 0 的时候就释放这个对象. 而 C 就没有这个问题, 因为 C 没有对象, 没有运行时的损耗.

那解决方式也就很简单了, 换一种新的内存管理模式就行了(Swift 实际做法是直接引入了一种新模式, 与 ARC 并存).

目前主流的内存管理方式分三种:

  1. 手动: C 语言的 malloc 库, 特点就是无运行时损耗, 但不好控制释放内存的时机.
  2. 半自动: Objective-C 和 Swift 的 MRC/ARC, 有运行时损耗, 但基本上可以让程序员不用去考虑内存管理的问题.
  3. 自动的: Java/Go 的 GC. 基本上同上, 但需要在某个时间点去停止所有线程, 释放内存的时机不可控.

Objective-C 的 MRC 还需要手动去写 retain/release, 在进化到 ARC 之后, 除了需要在类成员变量声明的时候, 显式地声明是 weak/strong/retain/copy 就可以了, retain/release 的插入交给编译器即可, ARC 其实已经是实际上的自动化内存管理模式了.

而 Swift 在把指针抽象为引用类型, 加入 Mutable/Immutable 的概念之后, 就只需要偶尔写写 weak 就行了, 唯一需要对于内存管理费心的就是 retain cycle 的问题, 但也比之前省心很多了. 而且随着 Swift 工具链的发展, 这些问题都可以在编译期或者 Debug 时就暴露出来.

半自动的内存管理, 实际上还有一种, 就是 Rust 的 OwnerShip, 我个人的理解是, 这种方式其实是 MRC/ARC 的一种延续, 但 MRC/ARC 内存释放的时机还是需要在运行时才能知道, 而 Rust 可以在编译期就解析出什么时候可以释放掉内存, 从而省略掉 retain/release 的存在, 也没必要专门跑一个 runtime 去监测对象的引用计数., 从而达到比 ARC 更高的运行效率

仔细思考一下这种自动化的内存管理模式, 其实都是在把指针分类, 加上 context(上下文), 抽象出来, 暴露给编译器更多与关于指针的信息, 而不是单纯的一个内存地址, 从而让编译器可以分析释放对象的时机.

Rust 也不例外, 既然要达到比 ARC 更高的运行效率, 那就必然要提供给编译器更多的指针信息, 以此提供给编译器更多的优化空间, 因此在指针声明和传递时都需要显式地声明所有权, 代码量也会相应地增多, 对于程序员的要求也会更高.

虽然写起来比 ARC 更麻烦一点, 但也比 C 那种原始的方式简单很多, 提供给了 Rust, Swift 这些”现代编程语言”编写底层程序的可能性.

补充: 学术界对于 GC 的研究已经很多了, 但关于 ARC 的研究还是很少, ARC 还有很大的进步空间. 甚至说如果可以在编译期就检测到对象实际释放的时机的话, 就可以直接省略掉中间的那些 retain/release, 不必在运行时再去检测是否应该释放掉这一段内存.

Swift 什么时候会引入这种内存管理模式

在我写这篇文章的时候, Swift 团队正式发布了引入 OwnerShip 的提案(正式方案?), 喵神大大也翻译了这篇文章, 更多技术细则可以去看喵神大大的翻译.

一句话总结: Swift 的团队希望 Swift 能够进化成为一门系统编程语言, 所以才不惜牺牲 ABI 稳定性去加入这个 Feature.

Swift 是为了取代 Objective-C 而生的吗

在 Cyan 大大的那篇文章下面, 有这么一条回复:

Screen Shot 2017-03-03 at 6.01.18 P

LLVM 之父, Swift 的作者之一 Chris Lattner 在 ATP 的一期访谈里聊过这件事情, 直接贴原话:

There’s a ton of stuff to love about Objective-C, and while there are a few things that are ugly about it, some “@“ signs and semicolons and other stuff like that, we can make Objective-C better. The question was always: Why not just make Objective-C better? Why don’t we just keep evolving Objective-C? Why do we want to face potential disruption in terms of moving the entire development community to something [23:00] new?

We kicked that around for a long time. We talked about both sides and we came to realize that, yes, we can and should make Objective-C better, and we continued to invest in Objective-C. We did things like ARC, for example, which is a major effort, but…

We were talking about, okay, can we just make Objective-C better and can we feature-creep it to the language we want for the fullness of time? Because if we can, that would be much less disruptive to the community. We decided that, yeah, we can move Objective-C a lot closer to what we want so we can get automatic memory management with ARC, for example, but we can’t ever take away the problems that lead to Objective-C being unsafe. The fundamental problem was Objective-C was built on top of C. [24:00] C inherently has pointers. It has uninitialized variables. It has array overflows. It has all these problems that even if you have full control of your compiler and tool stack, you just can’t fix. To fix dangling pointers, you would have to fix lifetime issues, and C doesn’t have a framework to reason about that, and retrofitting that into a compatible way into the system just wouldn’t really work.

If you took away C from Objective-C, you couldn’t use C arrays on the stack, for example. And if you [24:30] couldn’t do that, there’s entire classes of applications where the performance just wouldn’t be acceptable. We went around, around, around. We said the only way that this can make sense in terms of the cost of the disruption to the community is if we make it a safe programming language: not “safe” as in “you can have no bugs,” but “safe” in terms of memory safety while also providing high performance and moving the programming model forward. That was really kind [25:00] of the ideas that came together to make Swift being worth the investment and being worth being disruptive to the community. A lot of these kinds of pitches and ideas were being held in very small, small, small meetings. Coming out of WWDC 2013 is when we and the executive team decided okay, let’s really commit to this, and that’s when the developer-tools [25:30] team came to know about it and really started working hard on it.

— 节选自 Accidental Tech Podcast: Chris Lattner interview

内容有点长, 大家可以看一下我高亮的部分, 实际上苹果的团队也很犹豫, 到底要继续优化 Objective-C, 还是应该发明一门新的语言. 最后两种方式都尝试一下, 然后 Objective-C 就有了 ARC, 点语法等等新功能.

但最后, 苹果的团队发现 Objective-C 这门语言不安全最关键的原因还是因为它是基于 C 语言的, 它有指针, 它有不完全初始化的变量, 它会数组越界. 即使苹果的团队对于工具链和编译器有完整的控制权, 也没办法很好地解决这个问题.

苹果的团队想了又想, 反复思虑之后, 还是决定打断整个开发社区, 去创建一门 Safe 的编程语言, 不只是那种没有 bug的 Safe, 而是保持安全的同时还能提供高性能的, 推动整个编程范式前进的那种 Safe.

Swift 与 Objective-C 并非是对立的, “Objective-C is Great”, Swift 只是苹果提供的一个 “better option”.

ABI Stability vs. API Stability

关于是否应该主力 Swift 另一个关键的争论点在于, ABI 不稳定.

因为 Swift 的 ABI 不稳定而放弃 Swift 的人好像特别多. 可是我看到的很多文章都没有提到到底 ABI 稳定对于我们应用开发者代表着什么? ABI 与 API 又有什么区别?

ABI (Application Binary Interface)

在计算机中,应用二进制接口(英语:application binary interface,縮寫為 ABI)描述了应用程序(或者其他类型)和操作系统之间或其他应用程序的低级接口。

… ABI不同于应用程序接口(API),API定义了源代码和库之间的接口,因此同样的代码可以在支持这个API的任何系统中编译,然而ABI允许编译好的目标代码在使用兼容ABI的系统中无需改动就能运行。

https://zh.wikipedia.org/wiki/应用二进制接口

ABI 主要是描述程序跟操作系统之间的低级接口, 说白了就是Swift 二进制程序与系统或者其它程序交互时会调用的接口, 一般这部分都是由编译器去处理, 除非我们进行很底层的开发, 或者是想要 hack 编译过程(例如把 Swift 编译到 JavaScript) 才会需要去考虑这方面的东西.

ABI 的不稳定会造成以下结果:

  1. 打包 Swift 程序时, 必须嵌入一个 Swift 标准库.我们每次打包应用时, 都需要嵌入一个 Swift 的标准库, 因为系统不知道我们使用程序时用的 ABI 是哪个版本, 所以必须没办法在系统内部内置一套标准库. 用过 pyenv, rvm 或者 nvm 的人就大概知道这里的过程.
  2. 第三方 SDK 开发困难. 你的应用与第三方 SDK 使用的 ABI 版本如果不同就会出现问题, 例如说 Swift 2和 Swift 3打包出来的库就没办法互相调用. 非要支持的话, Swift 每出一个版本就需要跟着打包一个 SDK, 而且之前的 SDK 没办法向后兼容.

ABI 的稳定到底意味着什么

说明完了 ABI 是什么, 以及 ABI 不稳定会造成什么.

那 ABI 稳定对我们有什么实际意义? Chirs 在 ATP 里讨论过这个问题, 继续贴原话:

Another part of it is that ABI stability is super-important, but it’s not as important as people think it is for application developers. It’s really important to Apple, [51:30] but what we realized in the Swift 3 timeframe is that the thing app developers would benefit from the most was actually source stability. Who actually wants their application to be broken when they get a new version of Xcode? Really nobody, right?

ABI 稳定超级重要, 不过对于应用开发者来说, 并没有大家想象的那么重要. 但是对于苹果来说很重要. 我们相信在 Swift 3 之后, 应用开发者受益最多的还是代码稳定(API Stability), 谁愿意升级了一下 Xcode 就运行不了自己的程序呢?

ABI 的稳定对我们来说真的没有那么重要, 大厂开发 SDK 也只要选择 Objective-C 就行了(所以不能用 Swift 才是痛苦的地方?). 再给点力的话, 底层使用 Objective-C 实现, 用 Swift 负责暴露外部接口也行(例如 Instagram 开源的 IGListKit), Swift 版本迁移的工作就会大幅减少了. ABI 不稳定最痛苦的其实是苹果的底层开发人员, 他们必须实时去更新框架对外的接口, 想办法让官方框架去兼容不同版本的 Swift.

补充: 写这篇文章的时候, 柏学网翻译了 Swift 官方关于 ABI 稳定的声明, 大家可以去看看

API 稳定还会是问题吗

Halfway through the release, we pivoted and source stability became the goal, so I’m really excited that when Swift 3.1 or Swift 4 comes out that it’s still going to be able to build [52:00] Swift 3 code, and even if there are minor changes that need to be made for one reason or another, that you can upgrade and you have great compatibility with your old code and you don’t have to start the migrator before you can do anything. So it’s going to be a great improvement for people’s lives.

Swift 4的开发途中, 我们定下了一个目标, 无论是在 Swift 3.1 还是 Swift 4 里, 都必须可以编译 Swift 3的代码(编译器提供相应的编译模式)

API 不稳定导致的代码迁移, 可能 Swift 1 到 Swift 2 对于大家的冲击太大, 所以大家才会有那么深的怨念, 我没有经历过那一段日子没有发言权.

但 Swift 2 到 Swift 3 的迁移我是经历过的, 公司项目刚起步, 一万多行代码左右, 自动转换加上一个晚上我就让程序成功跑起来了, 随后一个星期修修 bug, 适配一下新的 Feature 也就完全过度过去了, 后来陆陆续续看了 Enjoy, Airbnb 等等关于 Swift 迁移过程的讲述, 过程其实也没有很痛苦. 但 CoreGraphic 等框架的 Swift 化带来的麻烦可能会更多一点, 代码耦合度太高的项目可能会在这里陷得很深.

Swift 3 之后会好很多, Swift 4的编译器提供了 Swift 3的编译模式, 至少我们可以慢慢地去迁移我们的代码, 而不是一升级 Xcode 就连程序也跑不起来.

API 之后肯定还会改的, 但其实从 Swift 2 到 Swift 3 的过程里, 我觉得很多原则性的东西其实已经稳定下来的, 接下来的改动要么就是很小, 要么就是特别有规律性, 迁移的时候花点时间去看看第三方发的迁移指南, 就可以很平稳地迁移过去了.

一句话总结: ABI 不稳定对于我们应用开发者的影响并没有那么大, API 之后虽然会变, 但之后肯定会给你充足的迁移时间

Swift 之我见

说了这么多, 希望大家现在可以理解 Swift 的团队到底是基于什么样的原因, 才做出了各种决策, 才演变出了现在的 Swift.

最后, 我想聊聊自己对于 Swift 的见解.

Swift 目前的问题

代码稳定, 其实都是小事情, 迁移, 几十万行代码, 分配给所有人, 最多也就是一个星期的事情. Swift 的开发者都很积极, 主流的第三方框架基本上两个星期内都能更新到最新的版本, 我用的库 75% 都从 beta 时期就开始跟进 Swift 的更新.

Swift 3 最大的问题我觉得还是工具链不稳定

  • 增量编译做的不好. 编译随时 Segment Fault, 必须 clean 一次才行.

  • 超长的编译时间. 每次编译出错, clean 之后可能需要编译七八分钟, 之前我记得还有人总结了一份什么样的语法会导致编译时间变长的列表, 为了编译时间缩短而却强行改变代码应有的样子, 我觉得很不值得.
    我采用的解决方式就是把底层模块抽出来, 独立成一个框架, 使用 Carthage 去进行包管理, 全部编译成静态库, debug 编译的时候, 只要链接上去就行了, 大大减少编译时间, 而且由于判断增量编译的工作量减少了, 索引速度也会大大提高, 代码补齐跟代码高亮会工作地更好一点. (顺带一说, Carthage 是用 Swift 写的)

  • 代码高亮随时崩, 代码补齐几乎没有. Xcode 很容易变白板, 我个人而言发生情况不多, 一天能遇上个两三次左右, 但是代码补齐就真心是完全没有, 每天基本上都是盲打, 只有像是 UICollectionElementKindSectionFooter 这种才会等等代码补齐. (补充一下, AppCode EAP 对于 Swift 3的支持异常的好, 补齐跟高亮都能够很好地满足日常需求, 我的 Air 8g 内存就能跑的很好)

这两个问题虽然很小很不起眼, 但对我日常工作影响是最大的.

之前 Chris Lattner 离开苹果加入特斯拉的消息大家应该都知道, 但 Chris 依旧保留自己在 Swift 开发小组的位置, 而实际上 Chris 在苹果也只有很小一部分时间分配给 Swift(LLVM 对于这个世界影响更大一点), 接任的 Ted, 实际上也是之前实际上的 Swift 开发小组组长, 他之前也是 Rust 的主要开发者之一, 第一版的 Clang 静态分析器也是 Ted 一个人撸出来的.

Chris 平时主要负责提一些”天马行空”的想法, 打乱掉开发小组的计划, Ted 负责把这些想法落地.

Ted 个人更倾向于优化编译, 提高开发体验, 觉得这件事情比起 ABI 稳定优先级更高, 但具体决策还是得根据具体情况去决定. 但既然都已经决定了支持 Swift 3 编译模式, 那估计优化工具链这件事情也会提到一个比较高的优先级, 至少不会像现在这么糟.

Swift 很 Apple

Swift 吸收了很多语言的特点, 以至于各种不同语言的开发者写 Swift 的时候都会觉得特别熟悉特别亲切. Chris 说 Swift 并不是想成为某一门语言的加强版, 也不是在模仿哪一门语言, Swift 只是想做到最好, 而想要做到最好就必须去吸收现有语言的优点, 通过某种方式去把它们糅合到一起. 类似的一句话, 乔布斯也说过, “我们不是刻意在模仿谁, 只是想做到最好, 如果做到最好需要学习别人的一些地方, 那就学习呗”, 这些都只是过程罢了, Best 才是结果.

Swift 的 ABI 不稳定, 影响最大的其实是苹果自己, 但因为 Swift 想成为更好的语言, 所以一再拖延. 延期, 回炉再造, 然后再拿出一个最好的”作品”, 就像苹果的其它作品.

Swift 还很不成熟, 各种意义上都不成熟, 需要做的事情很多, 泛型系统, 内存管理模型, 性能, 异步模型, POP 的探索, 编译的优化, ARC 的优化, 函数派发机制, 字符串处理, 更好的异常处理……

结语

Swift 对我来说是一种浪漫, 一种对于苹果文化的崇尚.

推荐阅读: