這個 Session 分爲兩個部分,前半部分會簡單介紹一下 Swift 開源相關的事情,後半部分咱們深刻了解一下 Swift 4.2 帶來了哪些更新。git
首先咱們來看一下 Swift 的一些統計數據,Swift 自開源以後,總共有 600 個代碼貢獻者,合併了超過 18k pull request。github
Swift 想要成爲一門跨平臺的泛用語言,大概一個月以前,Swift 團隊拓展了原有的公開集成平臺,叫作 Community-Hosted Continuous Integration,若是你們想要把 Swift 帶到其它平臺上,就能夠在這上面去接入大家本身的硬件機器,Swift 會按期在這些硬件上跑一遍集成測試,這樣你們就能夠很是及時地瞭解到最新的 Swift 是否能在大家的平臺上正常運行。正則表達式
目前已經接入了 Fedora 27 / Ubuntu on PowerPC / Debian 9.1 on Raspberry Pi 等等:算法
同時,Swift 的團隊付出了很大的精力在維護 Swift 的社區上,兩個月前 Swift 社區正式從郵件列表轉向論壇,讓你們能夠更容易貢獻本身的力量,例如說三月份的這一份提案:swift
你們只要簡單回答這些問題,參與討論便可,若是你對於這方面的理解不深,不想貿然發言的話,其實只要大概閱讀過社區成員們的發言,對這件事情有了解,那也是一種參與,之後也許這個提案出來了你還能夠寫篇文章跟你們講講當時討論的內容和要點。數組
若是你在維護一個 Swift 相關的計劃,你能夠考慮在論壇上申請一個板塊,讓社區的人也能夠關注到你的計劃而且參與到其中來。安全
Swift 的文檔如今改成由 swift.org 來維護,網址是 docs.swift.org。ruby
Chris Lattner 大神離開蘋果的時候,有不少人在討論這會不會對 Swift 的發展有很差的影響,但過去一年,實際上 Chris 也仍是盡本身的力量在推進 Swift 發展,去谷歌甚至能夠說是在那裏作 Swift 的佈道師。app
Chris 進了谷歌以後,谷歌 fork 了一個 Swift 的倉庫,做爲谷歌裏開發者 Commit 的中轉站,過去一年修復了不少 Swift 在 Linux 上的運行問題,讓 Swift 在 Linux 上的運行更加穩定。谷歌還寫了一個 Swift Formatter,如今正在開發階段。
而且促成了 Swift 與 Tensorflow 的合做,開發了 Swift for Tensorflow,主要是由於 Python 已經漸漸沒法知足 Tensorflow 的使用,上百萬次的學習循環讓性能表現變得異常重要,須要一門語言去跟 Tensorflow 有更緊密的交互,你們可能以爲其它語言也均可以使用 Tensorflow,沒有什麼特別,實際上其它語言都只是開發了 Tensorflow 的 API,而 Swift 代碼則會被直接編譯成 Tensorflow Graph,具備更強的性能,甚至 Tensorflow 團隊還爲 Swift 開發了專門的語法,讓 Swift 變成 Tensorflow 裏的一等公民。加入了與 Python 的交互以後,讓 Swift 在機器學習領域獲得了更加好的生態。
Chris 在過去一年,拉谷歌入局一塊兒維護 Swift,增強 Swift 在 Linux 上的表現,還給 Swift 開闢了一個機器學習的領域,而且在 Swift 社區持續活躍貢獻着本身的才華,如今我想你們徹底能夠沒必要擔憂說 Chris 的離開會對 Swift 產生什麼很差的影響。
而且 Swift 的開發團隊和社區裏也有不少作出了巨大貢獻的大神,例如這一次 Session 的主講之一 Slava,核心團隊負責人的 Ted,Doug Gregor,Xiaodi Wu 等等,他們也同樣把本身的精力和才華貢獻給了 Swift。
接下來咱們要了解一下 Swift 4.2,那麼 Swift 4.2 是什麼?它在整個開發週期中是一個什麼樣的角色?
Swift 每半年就會有一次 Major Release,Swift 4.2 就是繼 4.0 和 4.1 以後的一次 Major Release,官方團隊一直致力於提高開發體驗,這是 Swift 4.2 的開發目標:
Swift 5 會在 2019 年前期正式發佈,ABI 最終會在這一個版本里穩定下來,而且 Swift 的運行時也會內嵌到操做系統裏,到時候 App 的啓動速度會有進一步的提高,而且打包出來的程序也會變得更小。
若是你們對於 ABI 穩定的計劃感興趣的話,能夠關注一下這一份進度表 ABI Dashboard。
跟 Xcode 9 同樣,Xcode 10 裏也只會搭載一個 Swift 編譯器,而且提供兩種兼容模式,同時兼容以前的兩個 Swift 版本,這三種模式均可以使用新的 API,新的語言功能。
而且不僅是 Swift 的語法層面的兼容,開發組三種模式也同時覆蓋 SDK 的兼容,也就是說只要你的代碼在 Xcode 8,Swift 3 的環境下能跑,那麼在 Xcode 10 裏使用兼容模式也確定能夠跑起來。
但 Swift 4.2 確實提供了更多優秀的功能,爲了接下來的開發,這會是最後一個支持 Swift 3 兼容模式的版本。
接下來咱們來討論一下編譯速度的提高,這是在 Macbook Pro 四核 i7 上測試現有 App 的結果:
Wikipedia 是一個 Objective-C 和 Swift 混編的項目,可能更加貼近你們的實際項目,項目的編譯速度實際上取決於不少方面,例如說項目的配置,圖片文件的數量跟大小。
1.6 倍的提高是總體的速度,若是咱們只關注 Swift 的編譯時間的話,實際上它總共提高了 3 倍,對於很大一部分項目來講,一次全量編譯大概能夠比之前快兩倍。
這些提高來自於哪裏呢?因爲 Swift 裏並不須要導入頭文件,但每個文件由能夠訪問到模塊裏的其餘文件裏的內容,因此編譯階段會有大量的重複工做去進行 symbol 查找,此次編譯器構建了一個編譯 pipeline 去減小重複的跨文件執行。
另外這一次,把「編譯模式」從「優化級別」裏剝離了出來,編譯模式意味着咱們如何編譯咱們的模塊,目前總共有兩種模式:
增量編譯雖然全量編譯一次會比模塊化編譯慢,可是以後修改一次文件就只須要再編譯一次相關的文件便可,而沒必要整個模塊都從新編譯一次。
整個模塊一塊兒編譯的話會更加快,聽說原理是把全部文件都合併爲一個文件,而後再進行編譯,以此減小跨文件的 symbol 查找。但一旦改動了其中一個文件,就須要從新再把整個模塊編譯一遍。
增長了這個編譯選項實際上還有一個很重要的意義,之前咱們只有三種選項,能夠達到下面三種效果:
增量化編譯 | 模塊化編譯 | |
---|---|---|
優化 | ✅ | ✅ |
不優化 | ✅ | ❌ |
優化是須要消耗時間的的,如今咱們可使用模塊化而且不優化的選項,達到最快的編譯速度,把這個選項應用到咱們項目裏不常常改動的那一部分代碼裏的話(例如 pod 的依賴庫),就能夠大大提升咱們的編譯速度。
我把這個配置應用到項目裏以後,實測編譯速度從 113s 加快到了到了 64s,只要在 podfile 里加入這一段代碼就能夠了(在 Xcode 9.3 也能夠正常使用):
post_install do |installer|
# 提升 pod 庫編譯速度
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_COMPILATION_MODE'] = 'wholemodule'
if config.name == 'Debug'
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Onone'
else
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Osize'
end
end
end
end
複製代碼
Swift 使用 ARC 進行內存管理,ARC 是在 MRC 的基礎上演進出來的,ARC 使用某種對象管理模型在編譯時,在合適的位置自動爲咱們插入 retain 跟 release 代碼。
Swift 4.2 以前使用的模型是「持有(owned)」模型,調用方負責 retain,被調用方負責 release,換句話就是說被調用方持有了傳進來的對象,以下圖所示:
但實際上這種模型會產生不少沒必要要的 retain 跟 release,如今 Swift 4.2 改成使用「擔保(Guaranteed)」模型,由調用方去保證對象在函數調用的生命週期內不會被 release 掉,被調用方再也不持有對象:
採起了這種模型以後,不止能夠有更好的性能表現,還會讓編譯出來的二進制文件變得更小。
當咱們在 64bit 的平臺上實例化一個 String 的時候,它的長度是 16 bytes,爲了存儲不等長的內容,它會在堆裏申請一段空間去存儲,而那 16 個 bytes 裏會存儲着一些相關信息,例如編碼格式,這是權衡了性能和內存佔用以後的出來的結果。
但 16 bytes 的內存佔用實際上還存在着優化空間,對於一些足夠小的字符串,咱們徹底能夠沒必要在堆裏獨立存儲,而是放到這 16 個 bytes 裏空餘的部分,這樣就可讓小字符串有更好的性能和更少的內存佔用。
具體原理跟 NSString 的 Tagged Pointer 同樣,但能比 NSString 存放稍微更大一點的字符串。
Swift 還增長了一個優化等級選項 "Optimize for Size",名如其意就是優化尺寸,編譯器經過減小泛型特例化,減小函數內聯等等手段,讓最終編譯出來的二進制文件變得更小
現實中性能可能並不是人們最關心的,而應用的大小會更加劇要,使用了這個編譯選項實測可讓二進制文件減少 10-30%,而性能一般會多消耗 5%。
之前咱們爲了遍歷枚舉值,可能會本身去實現一個 allCases
的屬性:
enum LogLevel {
case warn
case info
static let allCases: [LogLevel] = [.warn, .info]
}
複製代碼
但咱們在添加新的 case 的時候可能會忘了去更新 allCases
,如今咱們在 Swift 4.2 裏可使用 CaseIterable
協議,讓編譯器自動爲咱們建立 allCases
:
enum LogLevel: CaseIterable {
case warn
case info
}
for level in LogLevel.allCases {
print(level)
}
複製代碼
Conditional Conformance 表達了這樣的一個語義:泛型類型在特定條件下會遵循一個特定的協議。例如,Array 只會在它的元素爲 Equatable 的時候遵循 Equatable:
extension Array: Equatable where Element: Equatable {
func ==<T : Equatable>(lhs: Array<Element>, rhs: Array<Element>) -> Bool { ... }
}
複製代碼
這是一個很是強勁的功能,Swift 標準庫裏大量使用這個功能,Codable 也是經過這個功能去進行檢查,幫助咱們自動生成解析代碼的.
與 Codable 相似,Swift 4.2 爲 Equatable
和 Hashable
引入了自動實現的功能:
struct Stock: Hashable {
var market: String
var code: String
}
複製代碼
但這會帶來一個問題,hashValue
該怎麼實現?現有 hashValue
的 API 雖然簡單,但卻難以實現,你必須想出一種方法去把全部屬性糅合起來而後產生一個哈希值,而且像 Set
和 Dictionary
這種圍繞哈希表構建起來的序列,性能徹底依賴於存儲的元素的哈希實現,這是不合理的。
在 Swift 4.2 裏,改進了 Hashable
的 API,引入了一個新的 Hasher
類型來存儲哈希算法,新的 Hashable
長這個樣子:
protocol Hashable {
func hash(into hasher: inout Hasher)
}
複製代碼
如今咱們再也不須要在實現 Hashable 的時候就決定好具體的哈希算法,而是決定哪些屬性去參與哈希的過程:
extension Stock: Hashable {
func hash(into hasher: inout Hasher) {
market.hash(into: &hasher)
code.hash(into: &hasher)
}
}
複製代碼
這樣 Dictionary
就再也不依賴於存儲元素的哈希實現,能夠本身選擇一個高效的哈希算法去構建 Hasher
,而後調用 hash(into:)
方法去得到元素的哈希值。
Swift 會在每次運行時 爲 Dictionary
和 Set
提供一個隨機的種子去產生隨機數做爲哈希的參數,因此 Dictionary
和 Set
都不會是一個有序的集合了,若是你的代碼裏依賴於它們的順序的話,那就修復一下了。
而若是你但願使用一個自定義的隨機種子的話,可使用環境變量 SWIFT_DETERMINISTIC_HASHING
去控制:
更多細節能夠查看 SE-0206 提案,不是很長,建議你們閱讀一遍。
隨機數的產生是一個很大的話題,一般它都須要系統去獲取運行環境中的變量去作爲隨機種子,這也造就了不一樣平臺上會有不一樣的隨機數 API:
#if os(iOS) || os(tvOS) || os(watchOS) || os(macOS)
return Int(arc4random())
#else
return random()
#endif
複製代碼
但開發者不太應該去關係這些這麼瑣碎的事情,雖然 Swift 4.2 裏最重要的是 ABI 兼容性的提高,但仍是實現了一套隨機數的 API:
let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
let randomFloat = Flow.random(in: 0 ..< 1)
let greetings = ["hey", "hi", "hello", "hola"]
print(greetings.randomElement()!)
let randomlyOrderGreetings = greetings.shuffled()
print(randomlyOrderedGreetings)
複製代碼
咱們如今能夠簡單地獲取一個隨機數,獲取數組裏的一個隨機元素,或者是把數組打亂,在蘋果的平臺上或者是 Linux 上隨機數的產生都是安全的。
而且你還能夠本身定義一個隨機數產生器:
struct CustomRandomNumberGenerator: RandomNumberGenerator { ... }
var generator = CustomRandomNumberGenerator()
let randomIntFrom0To10 = Int.random(in: 0 ..< 10, using: &generator)
let randomFloat = Flow.random(in: 0 ..< 1, using: &generator)
let greetings = ["hey", "hi", "hello", "hola"]
print(greetings.randomElement(using: &generator)!)
let randomlyOrderGreetings = greetings.shuffled(using: &generator)
print(randomlyOrderedGreetings)
複製代碼
以往咱們自定義一些跨平臺的代碼的時候,都是這麼判斷的:
#if os(iOS) || os(watchOS) || os(tvOS)
import UIKit
typealias Color = UIColor
#else
import AppKit
typealias Color = NSColor
#endif
extension Color { ... }
複製代碼
但實際上咱們關心的並非到底咱們的代碼能跑在什麼平臺上,而是它能導入什麼庫,因此 Swift 4.2 新增了一個判斷庫是否能導入的宏:
#if canImport(UIKit)
import UIKit
typealias Color = UIColor
#elseif canImport(AppKit)
import AppKit
typealias Color = NSColor
#else
#error("Unsupported platform")
#endif
複製代碼
而且 Swift 還新增了一套編譯宏可以讓咱們在代碼裏手動拋出編譯錯誤 #error("Error")
或者是編譯警告 #warn("Warning")
(之後再也不須要 FIXME 這種東西了)。
另外還增長了一套判斷運行環境的宏,下面是咱們判斷是否爲模擬器環境的代碼:
// Swift 4.2 之前
#if (os(iOS) || os(watchOS) || os(tvOS) &&
(cpu(i396) || cpu(x86_64))
...
#endif
// Swift 4.2
#if hasTargetEnviroment(simulator)
...
#endif
複製代碼
ImplicityUnwrappedOptional
又被稱爲強制解包可選類型,它實際上是一個非必要的工具,咱們使用它最主要的目的是,減小顯式的解包,例如說 UIViewController
的生命週期裏, view
在 init
的時候是一個空值,可是隻要 viewDidLoad
以後就會一直存在,若是咱們每次都使用都須要手動顯式強制解包 view!
就會很繁瑣,使用了 IUO 就能夠節省這一部分解包代碼。
因此 ImplicityUnwrappedOptional
是與 Objective-C 的 API 交互時頗有用的一個工具,全部未被標記上 nullability 的變量都會被做爲 IUO 類型暴露給 Swift,它的出現同時也是爲了暫時填補 Swift 里語言的未定義部分,去處理那些固定模式的代碼。隨着語言的發展,咱們應該明確 IUO 的做用,而且用好的方式去取代它。
SE-0054 提案就是爲此而提出的,這個提案實際上在 Swift 3 裏就實現了一部分了,在 Swift 4.2 裏繼續完善而且完整得實現了出來。
以往咱們標記 IUO 的時候,都是經過類型的形式去實現,在 Swift 4.2 以後,IUO 再也不是一個類型,而是一個標記,編譯器會經過給變量標記上 @_autounwrapped
去實現,全部被標記爲 IUO 的變量都由編譯器在編譯時進行隱式強制解包:
let x: Int! = 0 // x 被標記爲 IUO,類型其實仍是 Optional<Int>
let y = x + 1 // 實際上編譯時,編譯器會轉化爲 x! + 1 去進行編譯
複製代碼
這就更加符合咱們的本來的目的,由於咱們須要標記的是變量的 nullability,而經過類型去標記的話實際上咱們是在給一個值標記上 IUO,而並不是是變量。
固然,這樣的改變也會給以前的代碼帶來一點小影響,由於咱們標記的對象針對的是變量,而並不是類型,因此以往做爲類型存在的 IUO 就會變成非法的聲明:
let a: [Int!] = [] // 編譯不經過
複製代碼
同一時間內,代碼對於某一段內存空間的訪問是具備獨佔性,聽起來很難懂是吧,舉個例子你就明白了,在遍歷數組的同時對數組進行修改:
var a = [1, 2, 3]
for number in a {
a.append(number) // 產生未定義的行爲
}
複製代碼
Swift 經過內存獨佔訪問權的模型,能夠在編譯時檢測出這種錯誤,在 Swift 4.2 裏獲得增強,能夠檢測出更多非法內存訪問的狀況,而且提供了運行時的檢查,在將來,內存獨佔訪問權的檢查會像數組越界同樣默認開啓:
推薦查看Ole Begemann 大神的出品的 What's new in Swift 4.2,帶着你們用 Playground 親身體會一下 Swift 裏新的語法功能。
Swift 5 是一個很重要的里程碑,ABI 的穩定意味着這一份設計須要支撐後面好幾個大版本的功能需求,延期我以爲不算是一件壞事,你們別忘了,蘋果是 Swift 的最大的使用者,這門語言會支撐蘋果將來十幾年的 SDK 開發和生態,因此他們纔會在 ABI 穩定這件事情上更加謹慎當心,並且這也很符合今年蘋果的方針,穩中求進。
待 ABI 塵埃落定以後,Swift 的語法功能確定還會有一波爆發,async/await,原生的正則表達式...,甚至蘋果可能會開發 Swift Only 的 SDK,這些都讓我更加期待 2019 年的 Swift 5。