上一周有兩篇文章, 分別討論了大家在現(xiàn)階段到底該不該用 Swift
在這里我不是想給出一個答案該不該用 Swift, 只是想聊一聊我對于 Swift 的理解.
Swift 能不能取代 Objective-C?
那兩篇文章都討論了一個話題, Swift 到底能不能取代 Objective-C?
其實到現(xiàn)在為止 Swift 離替代 Objective-C 還是很遙遠塘安,因為 Apple 內(nèi)部一直在用 Objective-C 來做一些 Framework 的開發(fā)务甥,低層也不可能用 Swift 實現(xiàn)
— Cyan
如果把取代定義為編寫底層框架的話, 至少以 Swift 3 來說, 毫無疑問是不可能的.
首先我們先去探究一下為什么蘋果會選擇 Objective-C 作為官方開發(fā)語言?
Objective-C 是在 C 的基礎(chǔ)上加上一層面向?qū)ο? 在編寫高性能模塊的時候, 可以直接使用 C, 在編寫對于性能不高的業(yè)務(wù)模塊時, 就使用原生的 Objective-C.
目前蘋果的框架, 底層實現(xiàn)都是使用 C , 然后再套上一層 Objective-C 去暴露外部接口. 這種方式既保證了性能, 又能保證 API 的易用性.
而且蘋果使用了大量的開源框架, 系統(tǒng)底層很多模塊, 都已經(jīng)有了開源的基于 C 語言的框架實現(xiàn), 跟 C 的交互上, Swift 明顯不如 Objective-C 那么方便.
除開語言生態(tài), 還有一個問題是 Swift 的性能不如 C, Swift 在公布的時候, 就宣稱運行效率可以媲美甚至超越 C. 但這是有前提的, 沒有運行時損耗的情況下 Swift 才有可能與 C 的運行效率持平.
Swift 的運行時損耗到底來自哪里
Swift 的運行效率損耗主要來自于 ARC, 內(nèi)存管理, 運行的時候, 一個對象會不停地進行 retain 和 release, runtime 必須一直 observe 每一個對象的 retain count, 當 retain count 達到 0 的時候就釋放這個對象. 而 C 就沒有這個問題, 因為 C 沒有對象, 沒有運行時的損耗.
那解決方式也就很簡單了, 換一種新的內(nèi)存管理模式就行了(Swift 實際做法是直接引入了一種新模式, 與 ARC 并存).
目前主流的內(nèi)存管理方式分三種:
- 手動: C 語言的 malloc 庫, 特點就是無運行時損耗, 但不好控制釋放內(nèi)存的時機.
- 半自動: Objective-C 和 Swift 的 MRC/ARC, 有運行時損耗, 但基本上可以讓程序員不用去考慮內(nèi)存管理的問題.
- 自動的: Java/Go 的 GC. 基本上同上, 但需要在某個時間點去停止所有線程, 釋放內(nèi)存的時機不可控.
Objective-C 的 MRC 還需要手動去寫 retain/release, 在進化到 ARC 之后, 除了需要在類成員變量聲明的時候, 顯式地聲明是 weak
/strong
/retain
/copy
就可以了, retain/release 的插入交給編譯器即可, ARC 其實已經(jīng)是實際上的自動化內(nèi)存管理模式了.
而 Swift 在把指針抽象為引用類型, 加入 Mutable/Immutable 的概念之后, 就只需要偶爾寫寫 weak
就行了, 唯一需要對于內(nèi)存管理費心的就是 retain cycle 的問題, 但也比之前省心很多了. 而且隨著 Swift 工具鏈的發(fā)展, 這些問題都可以在編譯期或者 Debug 時就暴露出來.
半自動的內(nèi)存管理, 實際上還有一種, 就是 Rust 的 OwnerShip, 我個人的理解是, 這種方式其實是 MRC/ARC 的一種延續(xù), 但 MRC/ARC 內(nèi)存釋放的時機還是需要在運行時才能知道, 而 Rust 可以在編譯期就解析出什么時候可以釋放掉內(nèi)存, 從而省略掉 retain/release 的存在, 也沒必要專門跑一個 runtime 去監(jiān)測對象的引用計數(shù)., 從而達到比 ARC 更高的運行效率
仔細思考一下這種自動化的內(nèi)存管理模式, 其實都是在把指針分類, 加上 context(上下文), 抽象出來, 暴露給編譯器更多與關(guān)于指針的信息, 而不是單純的一個內(nèi)存地址, 從而讓編譯器可以分析釋放對象的時機.
Rust 也不例外, 既然要達到比 ARC 更高的運行效率, 那就必然要提供給編譯器更多的指針信息, 以此提供給編譯器更多的優(yōu)化空間, 因此在指針聲明和傳遞時都需要顯式地聲明所有權(quán), 代碼量也會相應(yīng)地增多, 對于程序員的要求也會更高.
雖然寫起來比 ARC 更麻煩一點, 但也比 C 那種原始的方式簡單很多, 提供給了 Rust, Swift 這些”現(xiàn)代編程語言”編寫底層程序的可能性.
補充: 學術(shù)界對于 GC 的研究已經(jīng)很多了, 但關(guān)于 ARC 的研究還是很少, ARC 還有很大的進步空間. 甚至說如果可以在編譯期就檢測到對象實際釋放的時機的話, 就可以直接省略掉中間的那些 retain/release, 不必在運行時再去檢測是否應(yīng)該釋放掉這一段內(nèi)存.
Swift 什么時候會引入這種內(nèi)存管理模式
在我寫這篇文章的時候, Swift 團隊正式發(fā)布了引入 OwnerShip 的提案(正式方案?), 喵神大大也翻譯了這篇文章, 更多技術(shù)細則可以去看喵神大大的翻譯.
一句話總結(jié): Swift 的團隊希望 Swift 能夠進化成為一門系統(tǒng)編程語言, 所以才不惜犧牲 ABI 穩(wěn)定性去加入這個 Feature.
Swift 是為了取代 Objective-C 而生的嗎
在 Cyan 大大的那篇文章下面, 有這么一條回復:
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.
內(nèi)容有點長, 大家可以看一下我高亮的部分, 實際上蘋果的團隊也很猶豫, 到底要繼續(xù)優(yōu)化 Objective-C, 還是應(yīng)該發(fā)明一門新的語言. 最后兩種方式都嘗試一下, 然后 Objective-C 就有了 ARC, 點語法等等新功能.
但最后, 蘋果的團隊發(fā)現(xiàn) Objective-C 這門語言不安全最關(guān)鍵的原因還是因為它是基于 C 語言的, 它有指針, 它有不完全初始化的變量, 它會數(shù)組越界. 即使蘋果的團隊對于工具鏈和編譯器有完整的控制權(quán), 也沒辦法很好地解決這個問題.
蘋果的團隊想了又想, 反復思慮之后, 還是決定打斷整個開發(fā)社區(qū), 去創(chuàng)建一門 Safe 的編程語言, 不只是那種沒有 bug的 Safe, 而是保持安全的同時還能提供高性能的, 推動整個編程范式前進的那種 Safe.
Swift 與 Objective-C 并非是對立的, “Objective-C is Great”, Swift 只是蘋果提供的一個 “better option”.
ABI Stability vs. API Stability
關(guān)于是否應(yīng)該主力 Swift 另一個關(guān)鍵的爭論點在于, ABI 不穩(wěn)定.
因為 Swift 的 ABI 不穩(wěn)定而放棄 Swift 的人好像特別多. 可是我看到的很多文章都沒有提到到底 ABI 穩(wěn)定對于我們應(yīng)用開發(fā)者代表著什么? ABI 與 API 又有什么區(qū)別?
ABI (Application Binary Interface)
在計算機中达椰,應(yīng)用二進制接口(英語:application binary interface墙懂,縮寫為 ABI)描述了應(yīng)用程序(或者其他類型)和操作系統(tǒng)之間或其他應(yīng)用程序的低級接口键畴。
… ABI不同于應(yīng)用程序接口(API),API定義了源代碼和庫之間的接口驻右,因此同樣的代碼可以在支持這個API的任何系統(tǒng)中編譯什黑,然而ABI允許編譯好的目標代碼在使用兼容ABI的系統(tǒng)中無需改動就能運行。
— https://zh.wikipedia.org/wiki/應(yīng)用二進制接口
ABI 主要是描述程序跟操作系統(tǒng)之間的低級接口, 說白了就是Swift 二進制程序與系統(tǒng)或者其它程序交互時會調(diào)用的接口, 一般這部分都是由編譯器去處理, 除非我們進行很底層的開發(fā), 或者是想要 hack 編譯過程(例如把 Swift 編譯到 JavaScript) 才會需要去考慮這方面的東西.
ABI 的不穩(wěn)定會造成以下結(jié)果:
- 打包 Swift 程序時, 必須嵌入一個 Swift 標準庫.我們每次打包應(yīng)用時, 都需要嵌入一個 Swift 的標準庫, 因為系統(tǒng)不知道我們使用程序時用的 ABI 是哪個版本, 所以必須沒辦法在系統(tǒng)內(nèi)部內(nèi)置一套標準庫. 用過 pyenv, rvm 或者 nvm 的人就大概知道這里的過程.
- 第三方 SDK 開發(fā)困難. 你的應(yīng)用與第三方 SDK 使用的 ABI 版本如果不同就會出現(xiàn)問題, 例如說 Swift 2和 Swift 3打包出來的庫就沒辦法互相調(diào)用. 非要支持的話, Swift 每出一個版本就需要跟著打包一個 SDK, 而且之前的 SDK 沒辦法向后兼容.
ABI 的穩(wěn)定到底意味著什么
說明完了 ABI 是什么, 以及 ABI 不穩(wěn)定會造成什么.
那 ABI 穩(wěn)定對我們有什么實際意義? Chirs 在 ATP 里討論過這個問題, 繼續(xù)貼原話:
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 穩(wěn)定超級重要, 不過對于應(yīng)用開發(fā)者來說, 并沒有大家想象的那么重要. 但是對于蘋果來說很重要. 我們相信在 Swift 3 之后, 應(yīng)用開發(fā)者受益最多的還是代碼穩(wěn)定(API Stability), 誰愿意升級了一下 Xcode 就運行不了自己的程序呢?
ABI 的穩(wěn)定對我們來說真的沒有那么重要, 大廠開發(fā) SDK 也只要選擇 Objective-C 就行了(所以不能用 Swift 才是痛苦的地方?). 再給點力的話, 底層使用 Objective-C 實現(xiàn), 用 Swift 負責暴露外部接口也行(例如 Instagram 開源的 [IGListKit](```
https://github.com/Instagram/IGListKit
> 補充: 寫這篇文章的時候, 柏學網(wǎng)翻譯了 [Swift 官方關(guān)于 ABI 穩(wěn)定的聲明](https://boxueio.com/black-board/1), 大家可以去看看
### API 穩(wěn)定還會是問題嗎
> 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的開發(fā)途中, 我們定下了一個目標, 無論是在 Swift 3.1 還是 Swift 4 里, 都必須可以編譯 Swift 3的代碼(編譯器提供相應(yīng)的編譯模式)
API 不穩(wěn)定導致的代碼遷移, 可能 Swift 1 到 Swift 2 對于大家的沖擊太大, 所以大家才會有那么深的怨念, 我沒有經(jīng)歷過那一段日子沒有發(fā)言權(quán).
但 Swift 2 到 Swift 3 的遷移我是經(jīng)歷過的, 公司項目剛起步, 一萬多行代碼左右, 自動轉(zhuǎn)換加上一個晚上我就讓程序成功跑起來了, 隨后一個星期修修 bug, 適配一下新的 Feature 也就完全過度過去了, 后來陸陸續(xù)續(xù)看了 Enjoy, Airbnb 等等關(guān)于 Swift 遷移過程的講述, 過程其實也沒有很痛苦. 但 CoreGraphic 等框架的 Swift 化帶來的麻煩可能會更多一點, 代碼耦合度太高的項目可能會在這里陷得很深.
Swift 3 之后會好很多, Swift 4的編譯器提供了 Swift 3的編譯模式, 至少我們可以慢慢地去遷移我們的代碼, 而不是一升級 Xcode 就連程序也跑不起來.
API 之后肯定還會改的, 但其實從 Swift 2 到 Swift 3 的過程里, 我覺得很多原則性的東西其實已經(jīng)穩(wěn)定下來的, 接下來的改動要么就是很小, 要么就是特別有規(guī)律性, 遷移的時候花點時間去看看第三方發(fā)的遷移指南, 就可以很平穩(wěn)地遷移過去了.
一句話總結(jié): **ABI 不穩(wěn)定對于我們應(yīng)用開發(fā)者的影響并沒有那么大, API 之后雖然會變, 但之后肯定會給你充足的遷移時間**
## Swift 之我見
說了這么多, 希望大家現(xiàn)在可以理解 Swift 的團隊到底是基于什么樣的原因, 才做出了各種決策, 才演變出了現(xiàn)在的 Swift.
最后, 我想聊聊自己對于 Swift 的見解.
### Swift 目前的問題
代碼穩(wěn)定, 其實都是小事情, 遷移, 幾十萬行代碼, 分配給所有人, 最多也就是一個星期的事情. Swift 的開發(fā)者都很積極, 主流的第三方框架基本上兩個星期內(nèi)都能更新到最新的版本, 我用的庫 75% 都從 beta 時期就開始跟進 Swift 的更新.
Swift 3 最大的問題我覺得還是工具鏈不穩(wěn)定
- **增量編譯做的不好**. 編譯隨時 Segment Fault, 必須 clean 一次才行.
- **超長的編譯時間**. 每次編譯出錯, clean 之后可能需要編譯七八分鐘, 之前我記得還有人總結(jié)了一份什么樣的語法會導致編譯時間變長的列表, 為了編譯時間縮短而卻強行改變代碼應(yīng)有的樣子, 我覺得很不值得.
我采用的解決方式就是把底層模塊抽出來, 獨立成一個框架, 使用 Carthage 去進行包管理, 全部編譯成靜態(tài)庫, debug 編譯的時候, 只要鏈接上去就行了, 大大減少編譯時間, 而且由于判斷增量編譯的工作量減少了, 索引速度也會大大提高, 代碼補齊跟代碼高亮會工作地更好一點. (順帶一說, Carthage 是用 Swift 寫的)
- **代碼高亮隨時崩, 代碼補齊幾乎沒有**. Xcode 很容易變白板, 我個人而言發(fā)生情況不多, 一天能遇上個兩三次左右, 但是代碼補齊就真心是完全沒有, 每天基本上都是盲打, 只有像是 `UICollectionElementKindSectionFooter` 這種才會等等代碼補齊. (補充一下, AppCode EAP 對于 Swift 3的支持異常的好, 補齊跟高亮都能夠很好地滿足日常需求, 我的 Air 8g 內(nèi)存就能跑的很好)
這兩個問題雖然很小很不起眼, 但對我日常工作影響是最大的.
之前 Chris Lattner 離開蘋果加入特斯拉的消息大家應(yīng)該都知道, 但 Chris 依舊保留自己在 Swift 開發(fā)小組的位置, 而實際上 Chris 在蘋果也只有很小一部分時間分配給 Swift(LLVM 對于這個世界影響更大一點), 接任的 Ted, 實際上也是之前實際上的 Swift 開發(fā)小組組長, 他之前也是 Rust 的主要開發(fā)者之一, 第一版的 Clang 靜態(tài)分析器也是 Ted 一個人擼出來的.
Chris 平時主要負責提一些”天馬行空”的想法, 打亂掉開發(fā)小組的計劃, Ted 負責把這些想法落地.
Ted 個人更傾向于優(yōu)化編譯, 提高開發(fā)體驗, 覺得這件事情比起 ABI 穩(wěn)定優(yōu)先級更高, 但具體決策還是得根據(jù)具體情況去決定. 但既然都已經(jīng)決定了支持 Swift 3 編譯模式, 那估計優(yōu)化工具鏈這件事情也會提到一個比較高的優(yōu)先級, 至少不會像現(xiàn)在這么糟.
### Swift 很 Apple
Swift 吸收了很多語言的特點, 以至于各種不同語言的開發(fā)者寫 Swift 的時候都會覺得特別熟悉特別親切. Chris 說 Swift 并不是想成為某一門語言的加強版, 也不是在模仿哪一門語言, Swift 只是想做到最好, 而想要做到最好就必須去吸收現(xiàn)有語言的優(yōu)點, 通過某種方式去把它們糅合到一起. 類似的一句話, 喬布斯也說過, “我們不是刻意在模仿誰, 只是想做到最好, 如果做到最好需要學習別人的一些地方, 那就學習唄”, 這些都只是過程罷了, Best 才是結(jié)果.
Swift 的 ABI 不穩(wěn)定, 影響最大的其實是蘋果自己, 但因為 Swift 想成為更好的語言, 所以一再拖延. 延期, 回爐再造, 然后再拿出一個最好的”作品”, 就像蘋果的其它作品.
Swift 還很不成熟, 各種意義上都不成熟, 需要做的事情很多, 泛型系統(tǒng), 內(nèi)存管理模型, 性能, 異步模型, POP 的探索, 編譯的優(yōu)化, ARC 的優(yōu)化, 函數(shù)派發(fā)機制, 字符串處理, 更好的異常處理……
## 結(jié)語
Swift 對我來說是一種浪漫, 一種對于蘋果文化的崇尚.
推薦閱讀:
- [ATP 播客 這一期訪問了 Chris Lattner](http://atp.fm/episodes/205): 喜歡 Swift 的同學絕對可以單曲循環(huán)四五次, 覺得聽英文困難的同學, 可以去看國內(nèi)大神出的新書 [老司機出品:iOS成長之路堪夭,非紙書-淘寶網(wǎng)](https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-16035181007.2.1XiP28&id=545914944810), 第一章就是前半段的翻譯.
- [喵神翻譯的 Swift 引入 Ownership 官方文章](https://onevcat.com/2017/02/ownership/): 具體技術(shù)細節(jié)我覺得比較其次, 但大概看一遍大家就可以對 API 修改的方向有點概念, 之后版本遷移也就不會一臉懵逼無從下手了.
- [譯:Swift ABI (一)](https://boxueio.com/black-board/1) : 泊學網(wǎng)翻譯的, Swift 官方對于 ABI 穩(wěn)定計劃的聲明. 泊學網(wǎng)里面內(nèi)容也挺不錯的, 推薦大家也稍微翻一翻, 我的第一印象就是里面的教學用了 AppCode, 教學的人肯定對于效率和工具有追求…