官方視頻地址:https://developer.apple.com/videos/play/wwdc2017/402/
在這個近1個小時的視頻里,我們看到了 Swift 4 的新特性,Swift 團隊的方向以及開源社區的強大力量。
整個視頻分為五個小節,分別為:
- 語言的優化與新功能(Language refinements and additions)
- 源碼兼容性(Source compatibility)
- 工具與性能(Tools and performance)
- 標準庫(Standard library)
- 內存的排他性存取(Exclusive access to memory)
下面我們就逐個解析吧。
一、語言的優化與新功能(Language refinements and additions)
-
extension
中可以訪問到被private
修飾的變量。這避免了有時將一些邏輯分離到extension
中卻訪問不了相關的變量,然后不得不將private
改成fileprivate
的尷尬。?? - 類與協議的組合。簡單來說就是下面的樣子:
var btns: [UIButton & MyProtocol]
用&
將類名和協議名組合起來,實現一些特定的能力。增加了POP的可用性,喜大普奔!同時,一些 OC 的 API 終于有了完美的 Swift 寫法,如視頻中的提及的:
二、源碼兼容性
Swift 4 的與 Swift 3 的差別不大,至少不像從 Swift 2 到 3 這么大。這下大家可以安心升級到 Swift 4 了吧?
在 Xcode 9 中,Swift 會以 4 和 3.2 兩個版本兼容存在。我們可以對不同的 target 選擇不同的版本進行編譯,比如:App 用 Swift 4 編譯,而某些第三方庫沒有更新,依然可以用 Swift 3.2 來編譯。
三、工具與性能
-
新的 Build System
更低的性能開銷(特別是在大項目上)。使用新的 Build System 目前需要在 Project Settings / Workspace Settings 中手動開啟,效果究竟如何有待商榷。
-
預編譯的橋接頭文件
在 Xcode 9 中,對混編項目的橋接頭文件(bridging header)進行了預編譯,生成了一個 precompiled header, 加快了對這些橋接頭文件的解析速度。在大項目中這個優化尤為有效,因為我們不再需要在每個 Swift 文件編譯時再對橋接頭文件編譯,也就是說,減少了無謂的、重復的編譯。這項優化在 Xcode 9 中默認開啟。
-
覆蓋測試中使用共用構建(Shared Build for Coverage Testing)
在 Xcode 8 中,執行測試構建會令工程構建兩次,其中一次是對整個工程的重新構建,以讓其包含冗余測試代碼 (extra instrumentation code)從而對代碼片段的執行次數進行計數;而另一次構建則是普通的構建,不包含冗余測試代碼。由于冗余測試代碼帶來的開銷非常小(less than 3%),因此在 Xcode 9 中,編譯器對此進行了優化,兩次構建變成了一次構建。
-
構建時索引(Indexing While Building)
在 Xcode 9 中,煩人的索引過程被挪到了構建時才執行。同樣開銷非常小。每次構建將更新索引,以實現更精準的索引結果。終于可以跟它說滾蛋了:
-
可預測的性能(Predictable Performance)
如果你看過去年 WWDC Session 416 Understanding Swift Performance 的話,應該知道什么是存在容器(Existential Container)。
簡單來說,就是協議類型在數組等集合類型中的數據結構。在存在容器中有一個緩存區,占 3 Words 的大小,在64位系統中就是 8 * 3 = 24 Bytes。如果存在容器裝得下某個小型的數據結構(比如有2個 Double 的 struct),那這個結構就會被存儲在緩存區里;否則會分配到堆中,然后將一個指向該堆地址的指針存儲在緩存區(棧)里。
于是就有了下面這張圖:4 Words 的 struct 對比只有 1、2、3 Words 的 struct,會有一個性能開銷迅速爬升的情況(performance cliff)。這就是因為涉及到了堆內存的分配。
視頻中還提到:“我們正在重新考量這個內聯緩存區的大小,但在 Swift 4 中,它依然是過去一樣占 3 個 Words 的大小”。
-
COW 存在容器
”那有 performance cliff 怎么辦呢?“
” 用??牛X版存在容器!“
為了解決這個問題,Swift 團隊優化了存在容器,使其有了 COW(copy-on-write,寫時復制)能力。如此一來,分配在堆中的緩存區也有了引用計數,多個存在容器可以共用一個緩存區。當要寫內存時再根據 COW 的規則來走,減少了堆內存開銷。 因此在 Swift 4 中,存在容器將有一個更穩定可靠的性能表現。
另外,對于泛型也有一項優化:將未具體化的泛型代碼(unspecialized generic code)所用的泛型緩存區(generic buffer)的內存分配位置,從原來的堆改成了 Swift 4 中的棧,以實現跟COW存在容器相似的優化效果。
-
更小的二進制包大小(Smaller Binaries)
減小二進制包大小也就意味著用戶的 App 更小。Swift 4 中有這幾個方面的優化讓我們的 App 瘦身:移除未使用的(協議)實現代碼。Swift 4 的編譯器可分析出哪些(協議)實現代碼是沒有被使用的,從而在打包時移除掉這些代碼。然而這個分析+移除的策略還不是那么完美,需要改動一下 Swift 的語法,也就是下面一項。
顯式寫 @objc 關鍵字以避免自動生成 Objective-C thunk 函數。原來啊,Swift 3 編譯時會幫 NSObject 的子類構建一份Objective-C 版本的 thunk 函數以供其 runtime 時調用。這些 thunk 函數雖 然最終還是調用 Swift 的實現,但也占二進制大小,還讓(1)中的優化無法實現。因此在 Swift 4 + Objective-C 的混編項目中,需要顯式寫 @objc 關鍵字,暴露需要用到的 Swift 方法。如果有多個方法要暴露給 Objective-C,建議你把它們放入 @objc extension 中。
-
剝除 Swift 符號 。
Swift 團隊為 libswiftCore 等核心庫減小了 symbol 的大小占用,方法是使用更簡潔的命名和剝除 Swift 符號。
?
至于剝除符號(Symbol Stripping)的原因,請允許我翻譯一下視頻中的原話:
"靜態鏈接器和動態鏈接器分別用自己的字典樹來快速查找符號,也就是說 Swift 的 symbols 放在符號表中是基本上沒用的。"
因此在 Xcode 9 中,Strip Swift Symbols
默認開啟以優化我們工程中自己的 Swift 代碼;而對于系統庫的 Swift 代碼,則在 App Thinning 中才進行符號剝離,同樣有一個Strip Swift Symbols
的選項可供勾選。
四、標準庫
這一小節主要講了Swift 字符串的優化、一些新語法和新的泛型特性。
-
Swift 4 處理復雜字素將更快(是 Swift 3 的3倍效率)。在 Swift 中,一個
Character
即一個字素(Grapheme)。所謂的“復雜字素”,從表面上看,是指如emoji、拉丁字母、漢字、日語假名等非英文亦非簡單字符的字素;從底層看,是那些無法通過下標隨機存取的字符集合。像這樣:var famaily = "??" famaily += "\u{200D}??" famaily += "\u{200D}??" famaily += "\u{200D}??" print(famaily) // ??????????? print(famaily.characters.count) // Swift3輸出“4”,Swift4輸出“1“
Swift 4 的字符串本身就是一個字符集合。也就是說,不需要再寫
str.characters.count
了,直接str.count
。-
一個新語法:
str.startIndex...
,表示從str
的startIndex
到其endIndex
。再舉個粟子:
-
截取部分字符串的 API 變了,返回的類型也變了。
let str = "one,two,three" // Swift 3 str.components(separatedBy: ",") // 返回 Array<String> // Swift 4 str.split(separator: ",") // 返回新類型 Array<Substring>
此舉有利有弊。
利:不再執行深拷貝,減少內存分配和性能開銷;
弊:原來的字符串大哥有可能被其切片后的小弟保持強引用,導致大哥釋放不了。
因此,要在適當的時候顯式將
Substring
轉成String
!
String與Substring共享緩存.png
- 使用一對三雙引(""")來包裝多行字符串字面量。如下圖所示,注意縮進:(開源的力量!??)
新的泛型特性:
protocol
的associatedtype
可以使用where
子句,以實現對不同associatedtype
之間的約束。視頻給出了 Swift 標準庫中利用這個新特性優化了Sequence
和Collection
的案例。-
新的泛型特性:泛型下標(Generic Subscripts)。也就是說可以這樣:
extension Bar { subscript<T>(t: T) -> Foo { //... } }
視頻給出了 Swift 標準庫中利用這個新特性實現了
…
(上面第3點)的案例。?
五、內存的排他性存取(Exclusive access to memory)
這是一個 Swift 4 的新特性,簡單而言就是,對有值語義(value types)的集合類型變量的寫操作時,不能同時再觸發另一讀寫操作。或者也可以理解成,不能再觸發 copy-on-write 機制。
也就是說,不讓下面這種事發生!在迭代時,閉包引用了numbers
的緩存區,希望修改numbers
內元素的值。如果此時執行numbers = []
,那就會報錯:排他性存取!
排他性存取的檢測分為編譯時和運行時兩種。編譯時檢測可直接報錯;運行時檢測則會拋出異常,如下圖。
有點像多線程的問題對吧?至此,我們只是在單線程下看這個排他性存取,而如果在多線程下觸發運行時的排他性存取,那就要通過 Thread Sanitizer 處理了。
當前,在 Swift 3.2 下的非排他性存取只會報 warning,但在未來的 Xcode 中會升級為 error。
內存的排他性存取保證了安全性的同時,也為從中優化了標準庫的設計。
在工程設置中可以調整 Exclusive Access to Memory 的策略。
總結
Swift 4 在 Swift 3 的基礎上升級,沒有像去年那樣巨大的遷移成本。升級后的 Swift 更快速、更安全,配合著 Xcode 9,生成的 App 體積將更小。
最后
此文粗略,或有錯漏,煩請指明,當天修正!
終于寫完了!全程無字幕聽著畫重點,哈哈!??
Let's Swift!??