無論你是否主力 Swift, 這些事情你都可以瞭解一下

四娘發表於2019-02-28

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 而生的嗎

無論你是否主力 Swift, 這些事情你都可以瞭解一下

在 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.

— 節選自 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的系統中無需改動就能執行。

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 開發團隊的時間可能不會特別多了, 加緊開發功能性的東西, 趕快穩定 ABI 可能會更加重要.(只是個人感覺, 單純的個人感覺)

Swift 目前的熱度主要還是因為 iOS 平臺的強勢, 繼續加強什麼語法糖, 優化開發體驗, 最終也只會把 Swift 坑進 iOS 裡, 只有讓 Swift 變得更強大, 能夠在更多領域有所發揮, 才能反哺 Swift, 讓 Swift 變成一門偉大的語言.

Swift 很 Apple

Swift 吸收了很多語言的特點, 以至於各種不同語言的開發者寫 Swift 的時候都會覺得特別熟悉特別親切. Chris 說 Swift 並不是想成為某一門語言的加強版, 也不是在模仿哪一門語言, Swift 只是想做到最好, 而想要做到最好就必須去吸收現有語言的優點, 通過某種方式去把它們糅合到一起. 類似的一句話, 賈伯斯也說過, “我們不是刻意在模仿誰, 只是想做到最好, 如果做到最好需要學習別人的一些地方, 那就學習唄”, 這些都只是過程罷了, Best 才是結果.

Swift 的 ABI 不穩定, 影響最大的其實是蘋果自己, 但因為 Swift 想成為更好的語言, 所以一再拖延. 延期, 回爐再造, 然後再拿出一個最好的”作品”, 就像蘋果的其它作品.

Swift 還很不成熟, 各種意義上都不成熟, 需要做的事情很多, 泛型系統, 記憶體管理模型, 效能, 非同步模型, POP 的探索, 編譯的優化, ARC 的優化, 函式派發機制, 字串處理, 更好的異常處理……

結語

Swift 對我來說是一種浪漫, 一種對於蘋果文化的崇尚.

推薦閱讀:

相關文章