概覽
Swift 5 發(fā)布了,這是一個重要里程碑。
此版本終于迎來了 ABI 穩(wěn)定,因此 Swift 運行時現(xiàn)在可以引入到 Apple 平臺各類操作系統(tǒng)的不同版本中,包括 macOS、iOS、tvOS 與 watchOS。Swift 5 還引入了構(gòu)建塊的新功能,包括重新實現(xiàn) String、在運行時對執(zhí)行內(nèi)存的獨占訪問與新數(shù)據(jù)類型,以及對動態(tài)可調(diào)用類型的支持。
Swift 5 兼容 Swift 4、Swift 4.1 和 Swift 4.2,Xcode 10.2 中包含了一個代碼遷移器,可以自動處理許多遷移需要用到的源碼更改。
最重要的更新:ABI穩(wěn)定
- 什么是ABI。
在運行時,Swift程序二進制文件通過ABI與其他庫和組件交互,ABI定義了很多底層細節(jié):如何調(diào)用函數(shù),如何在內(nèi)存中表示數(shù)據(jù),甚至是元數(shù)據(jù)的位置以及如何訪問它。 - 什么是ABI穩(wěn)定
此前發(fā)布的Swift版本中ABI還沒穩(wěn)定,Swift并沒包含在操作系統(tǒng)中,所以每一個應(yīng)用內(nèi)部都包含其Swift版本所對應(yīng)的動態(tài)鏈接庫。
Swift 5將為Swift的標準庫提供穩(wěn)定的ABI,Swift將包含在操作系統(tǒng)中,新版本的編譯器會根據(jù)穩(wěn)定的ABI來編譯生成的應(yīng)用程序二進制文件。也就是說,以后新發(fā)布的編譯器也可以編譯舊版Swift 5代碼,源代碼實現(xiàn)兼容。 - ABI穩(wěn)定帶來的便利
- 因為源代碼兼容,開發(fā)者能夠跨多個Swift版本維護單個代碼庫;
- Swift應(yīng)用程序包會變得更小;
- Swift語言變化以及變化頻率都會有所下降;
- ABI穩(wěn)定下階段的目標
關(guān)于Swift ABI 穩(wěn)定規(guī)劃,Swift 5完成的是第一階段的源兼容(Source compatibility),下半部分是二進制framework和運行時兼容(Binary framework & runtime compatibility),詳細描述可見宣言Swift ABI Stability Manifesto。
新特性
Raw Strings (原始字符串)
SE-0200,增加了創(chuàng)建原始字符串的功能,使得寫帶有特殊符號的字符串更加簡單。
- 要使用原始字符串, 可使用
#將字符串包裹起來;
let str = #"This is also a Swift string literal"#
- 原始字符串中反斜杠和雙引號會被視為字符串中的文字字符;
// before
let rain = "The \"rain\" in \"Spain\" falls \\mainly on the Spaniards."
// after
let rain = #"The "rain" in "Spain" falls \mainly on the Spaniards."#
- 由于反斜杠作為原始字符串中的字符,所以在插入值的時候需要在后面再加個 #;
let answer = 42
// before
let dontpanic = "The answer to life, the universe, and everything is \(answer)"
// after
let dontpanic = #"The answer to life, the universe, and everything is \#(answer)"#
- 如果需要在原始字符串包含 # 時, 前后應(yīng)用 ## 包裹字符串
let str = ##"My dog said "woof"#gooddog"##
- 多行原始字符串用 #""" 開頭 """#結(jié)尾
let multiline = #"""
The answer to life,
the universe,
and everything is \#(answer).
"""#
- 由于不用反斜杠轉(zhuǎn)義,使得正則表達式更加簡潔明了
// before
let regex1 = "\\\\[A-Z]+[A-Za-z]+\\.[a-z]+"
// after
let regex2 = #"\\[A-Z]+[A-Za-z]+\.[a-z]+"#
標準類型Result
SE-0235,增加Result類型到Swift標準庫,用于統(tǒng)一在異步完成處理程序中看到的笨拙的不同參數(shù)。
public enum Result<Success, Failure: Error> {
case success(Success), failure(Failure)
}
例如,在URLSession完成處理包含三個參數(shù),處理起來就不怎么優(yōu)雅:
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error != nil else { self.handleError(error!) }
guard let data = data, let response = response else { return /* Impossible? */ }
handleResponse(response, data: data)
}
這幾行代碼暴露了Swift缺乏對錯誤自動處理的缺點,在這里不僅是因為需要對error做處理,而且缺少比如data和response都為空等特殊情況的處理,使用Result則會非常的優(yōu)雅:
URLSession.shared.dataTask(with: url) { (result: Result<(response: URLResponse, data: Data), Error>) in
switch result {
case let .success(success):
handleResponse(success.response, data: success.data)
case let .error(error):
handleError(error)
}
}
自定義字符串插值
SE-0228大大改進了Swift的字符串插值系統(tǒng),使其更高效,更靈活。
主要用法就是在String.StringInterpolation的拓展里添加自定義的插值方法,比如加一個格式化字符串的方法:
extension String.StringInterpolation {
public mutating func appendInterpolation(_ number: Double?, format formatString: String) {
if let number = number{
return appendLiteral(String(format: formatString, number))
} else {
return appendLiteral("nil")
}
}
}
然后就可以在字符串中使用自定義的插值方法
let number = 123.666
print("Hello, number is \(number")
print("Hello, number is \(number, format:"%.2f")")
print("Hello, number is \(nil, format:"%.2f")")
============================================================================
Hello, number is 123.666
Hello, number is 123.67
Hello, number is nil
所有字符串的處理、統(tǒng)計、添加html屬性等操作都可以直接通過新的插值方式實現(xiàn)。
Dynamically callable types(動態(tài)可調(diào)用類型)
SE-0216@dynamicCallable為Swift 添加了一個新屬性,它帶來了將類型標記為可直接調(diào)用的功能,支持應(yīng)用于結(jié)構(gòu),枚舉,類和協(xié)議,但擴展不可以。
如果需要添加@dynamicCallable屬性, 就必須要實現(xiàn)以下方法中的一個或者兩個:
// 不需要指定參數(shù)名
func dynamicallyCall(withArguments args: [Int]) -> Double
// 指定參數(shù)名的方法
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double
對比Swift 5之前的定義和調(diào)用方式:
// 定義方式
struct RandomNumberGenerator {
func generate(numberOfZeroes: Int) -> Double {
let maximum = pow(10, Double(numberOfZeroes))
return Double.random(in: 0...maximum)
}
}
// 調(diào)用方式
let random = RandomNumberGenerator()
let num = random.generate(numberOfZeroes: 2)
使用Swift 5 的@dynamicCallable屬性
// 定義方式
@dynamicCallable
struct RandomNumberGenerator {
func dynamicallyCall(withArguments args: [Int]) -> Double {
let numberOfZeroes = Double(args.first ?? 0)
let maximum = pow(10, numberOfZeroes)
return Double.random(in: 0...maximum)
}
}
// 調(diào)用方式
let random = RandomNumberGenerator()
let num = random(2) // random(2)等同于random.dynamicallyCall(withArguments: [2])
處理未來添加的枚舉類型
SE-0192 在枚舉新增加一個 @unknown 屬性,用于區(qū)分兩種稍有不同的情況:1. default 的 case 里面處理所有其他的 case;2. 需要單獨處理所有 case,只有真正不符合所有 case,才會進入 default提示。比如:
enum PasswordError: Error {
case short
case obvious
case simple
}
func showOld(error: PasswordError){
switch error {
case .short:
print("Your password was too short.")
case .obvious:
print("Your password was too obvious.")
default:
print("Your password was too simple.")
}
}
上面代碼假如我們再加個 case old,執(zhí)行代碼時它會自動進入到default分支,這是顯然是,因為這個密碼是一個舊密碼而不是密碼太簡單,這時候可以用 @unknown,如下
func showNew(error: PasswordError) {
switch error {
case .short:
print("Your password was too short.")
case .obvious:
print("Your password was too obvious.")
@unknown default:
print("Your password wasn't suitable.")
}
}
這時Xcode會產(chǎn)生一個警告??Switch must be exhaustive. Do you want to add missing cases?,因為新增了case old ,switch沒有明確地處理每一個分支。
可變參數(shù)
在Swift 5之前,可以編寫一個帶有可變參數(shù)的枚舉, 如下:
enum X {
case foo(bar: Int...)
}
func baz() -> X {
return .foo(bar: 0, 1, 2, 3)
}
在Swift 5之后, 上述定義改成數(shù)組參數(shù), 而不是可變參數(shù), 如下:
enum X {
case foo(bar: [Int])
}
func baz() -> X {
return .foo(bar: [0, 1, 2, 3])
}
try?可選值嵌套
SE-0230 讓try?返回的多層 optional value嵌套值變成只有一層,無論有多少可嵌套的可選值,返回值永遠只是一個可選值。先看一下這個例子
struct User {
var id: Int
init?(id: Int) {
if id < 1 {
return nil
}
self.id = id
}
func getMessages() throws -> String {
// complicated code here
return "No messages"
}
}
let user = User(id: 1)
let messages = try? user?.getMessages()
在Swift4.2及其之前的版本中,上面返回的是一個String??,2層嵌套的可選值,如果有多層嵌套處理起來也是相當更麻煩。在Swift 5中就完美的解決了這個問題,如果當前值是可選的,那么try?將不會將值包裝在可選值中,因此最終結(jié)果只是一個String?。
整型倍數(shù)判斷
SE-0225為整數(shù)類型添加了一個方法isMultiple(of:),可以檢查一個整數(shù)是否為另一個整數(shù)的倍數(shù)。
let rowNumber = 4
if rowNumber.isMultiple(of: 2) {
print("Even")
} else {
print("Odd")
}
count函數(shù)
SE-0220,在Swift之前的版本中,有一個函數(shù)filter可以過濾出數(shù)組中符合條件的的元素,組成一個新的數(shù)組,在Swift 5中新增了一個函數(shù)count(where:), 可以獲取數(shù)組中符合條件的元素的個數(shù)。
let arr = [1, 2, 34, 5, 6, 7, 8, 12, 45, 6, 9]
let filter = arr.filter({ $0 > 10 })
print(filter) // [34, 12, 45]
let count = arr.count(where: { $0 > 10 })
print(count) // 3
compactMapValues過濾字典元素
SE-0218compactMapValues()為字典添加了一種新方法,將Swift4.x的版本有兩個函數(shù)compactMap和mapValue結(jié)合在一起。
-
compactMap: 返回一個操作后得到的新的數(shù)組, 類似flatMap -
mapValues: 字典中的函數(shù), 對字典的value值執(zhí)行操作, 返回改變value后的新的字典
let times = [
"first": 2,
"second": 43,
"three": 12,
"four": 3
]
let compact = times.compactMap({ $0.value > 10 })
// [true, false, true, false]
let mapValues = times.mapValues({ $0 + 2 })
// ["second": 45, "first": 4, "three": 14, "four": 5]
compactMapValues是將上述兩個方法的功能合并在一起, 返回一個對value操作后的新字典, 并且自動過濾不符合條件的鍵值對,下面的例子展示把非整形類型的鍵值對過濾掉:
let times = [
"Hudson": "38",
"Clarke": "42",
"Robinson": "35",
"Hartis": "DNF"
]
let finishers1 = times.compactMapValues { Int($0) }
// ["Clarke": 42, "Robinson": 35, "Hudson": 38]
參看文獻: