Swift 5.0新特性更新
-
Swift 5.0
是Swift
中最備受關注的一個版本, 傳說中API
穩定的版本 - 上個版本Swift 4.1 的新特性中介紹了條件一致性和雜湊索引等相關更新
- 隨著
Xcode Bate 10.2
的釋出,Swift 5.0
也釋出了測試版, 相信也帶來了很多優化和改進 - 下面執行環境都是在
Xcode Bate 10.2
環境中進行的
新特性
dynamicCallable
- SE-0216
-
@dynamicCallable
為Swift
添加了一個新屬性, 允許使用一個簡單的語法糖呼叫函式一樣呼叫命名型別 - 如果需要新增
@dynamicCallable
屬性, 就必須要實現以下方法中的一個或者兩個
func dynamicallyCall(withArguments args: [Int]) -> Double func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double
注意點
- 除了接受各種輸入外,您還可以為各種輸出提供多個過載, 自定義返回值, 可以是
String
,Int
等等…… -
KeyValuePairs
的使用和介紹, 沒有使用過的可參考
下面看一個例子, RandomNumberGenerator
生成一個隨機數
Swift 5.0
之前的定義和呼叫方式
// 定義方式 struct RandomNumberGenerator { func generate(numberOfZeroes: Int) -> Double { let maximum = pow(10, Double(numberOfZeroes)) return Double.random(in: 0...maximum) } } // 呼叫方式 let random = RandomNumberGenerator() let num = random.generate(numberOfZeroes: 2) print(num)
在 Swift 5.0
使用 @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) } } // 呼叫方式 let random = RandomNumberGenerator() let num = random(2) // random(2)等同於random.dynamicallyCall(withArguments: [2]) print(num)
@dynamicCallable
使用注意事項
- 可以將它應用於結構,列舉,類和協議。
- 如果你實現
withKeywordArguments:
並且沒有實現withArguments:
,你仍然可以在沒有引數標籤的情況下呼叫 - 如果你的實現
withKeywordArguments:
或withArguments:
時標記為throw
,則呼叫該型別也將被丟擲throw
- 副檔名無法新增
@dynamicCallable
,只能新增到主要型別上 - 仍然可以為你定義的型別新增其他方法和屬性,並且能夠正常使用
WritableKeyPath
- SE-0227
- 新增引用標識鍵路徑的功能,該路徑指的是應用它的整個輸入值
-
Swift
中的每個值都有一個特殊的偽屬性.self,它指的是整個值
let id = \Int.self var x = 1 print(id)////Swift.WritableKeyPath<Swift.Int, Swift.Int> x.self = 2 print(x)//2 print(x.self)//2 print(x[keyPath: id])//2 x[keyPath: id] = 3 print(x[keyPath: id])//3
可選引數
在 Swift 5
之前,可以編寫一個帶有可變引數的列舉, 但是在 Swift 5
開始, 呼叫時會報錯, 如下
enum X { // 此處定義切沒有呼叫時不會報錯 case foo(bar: Int...) } func baz() -> X { // 此處呼叫時會報錯 return .foo(bar: 0, 1, 2, 3) }
在 Swift 5
之後, 上述定義改成陣列引數, 而不是可變引數, 如下
enum X { case foo(bar: [Int]) } func baz() -> X { return .foo(bar: [0, 1, 2, 3]) }
Raw Strings
\
處理
- SE-0200 增加了建立原始字串的功能,其中反斜槓和引號被解釋為文字元號,而不是轉義字元或字串終止符
- 單行字串文字可以用反斜槓填充, 以保證原字串, 否則會報錯
// 文字引用型別 // 錯誤寫法 let quote = "Alice: "How long is forever?" White Rabbit: "Sometimes, just one second."" // 正確寫法 let quote1 = "Alice: \"How long is forever?\" White Rabbit: \"Sometimes, just one second.\"" // 正則表法式型別 // 錯誤寫法 let ucCaseCheck = "enum\s+.+\{.*case\s+[:upper:]" // 正確寫法 let ucCaseCheck = "enum\\s+.+\\{.*case\\s+[:upper:]"
#
處理
- 要使用原始字串, 可使用
#
將字串包裹起來 -
#
字串開頭和結尾的符號成為字串分隔符的一部分,因此如下Swift
理解“rain”
和“Spain”
周圍的獨立引號應該被視為文字引號而不是結束字串 - 原始字串也允許使用反斜槓, 但是將反斜槓視為字串中的文字字元,而不是轉義字元
let rain = #"The "rain" in "Spain" falls mainly on the Spaniards."# let keypaths = #"Swift keypaths such as \Person.name hold uninvoked references to properties."# let answer = 42 let dontpanic = #"The answer to life, the universe, and everything is \#(answer)."#
注意
- 上面使用
\#(answer)
引用變數而不是\(answer)
, 因為在用#
包裹的字串中反斜槓將會被失敗別為文字字元而不是轉義字元, 所以必須額外的新增#
##
處理
- 在字串的開頭和結尾使用
#
處理, 在字串中可以使用反斜槓等特殊字元, 那如果字串中需要使用#
, 又該如何處理?? - 使用
#
包裹字串, 預設會以#
為字串的結束符號,#
後面的文字將不再處理, 在這種情況下, 我們會使用##
處理 - 注意: 字串的開頭和結尾的標識必須一樣
let str = ##"My dog said "woof"#gooddog"##
多行字串
原始字串與 Swift
的多行字串系統完全相容 - 只需用於 #"""
啟動,然後 """#
結束
let multiline = #""" The answer to life, the universe, and everything is \#(answer). """#
try?
巢狀
先看下面程式碼
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" } }
在 Swift4.2
及其之前的版本中
let user = User(id: 1) // 這裡得到的message的型別是: let messages: String?? let messages = try? user?.getMessages() // 如果我們想得到非可選值就需要 print((messages ?? "") ?? "") // 或者多次強解, 當然不建議強解寫法 print(messages!!)
- 在
Swift4.2
及其之前的版本中, 上面返回的是一個2層巢狀的可選值, 如果有多層巢狀處理起來也是相當更麻煩的 - 在
Swift 5
中就完美的解決了這個問題, 如果當前值是可選的, 那麼try?
將不會將值包裝在可選值中, 因此最終結果只是一個String?
- 因此在
Swift 5
中無論有多少可巢狀的可選最後, 返回值永遠只是一個可選值 - 同樣,如果你使用了可選的連結
as?
,你仍然只有一個級別的可選性
let user = User(id: 1) // 型別: let messages: String? let messages = try? user?.getMessages() print(messages ?? "")
isMultiple
- SE-0225 為整數型別添加了一個方法
isMultiple
- 該方法可以檢查一個證書是否為另一個整數的倍數
let rowNumber = 4 if rowNumber.isMultiple(of: 2) { print("Even") } else { print("Odd") } // 該方法等效於 if rowNumber % 2 == 0 {}
count
- SE-0220
- 在
Swift
之前的版本中, 有一個函式filter
可以過濾出陣列中符合條件的的元素, 組成一個新的陣列, 詳細使用可參考 Swift函數語言程式設計之高階用法 - 在
Swift 5
中新增了一個函式count(where:)
, 可以獲取陣列中符合條件的元素的個數
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
- 在
Swift4.x
的版本有兩個函式compactMap
和mapValues
-
compactMap
: 返回一個操作後得到的新的陣列, 類似flatMap
-
mapValues
: 字典中的函式, 對字典的value
值執行操作, 返回改變value
後的新的字典
let times = [ "first": 2, "second": 43, "three": 12, "four": 3 ] let compact = times.compactMap({ $0.value > 10 }) print(compact) // [true, false, true, false] let mapValues = times.mapValues({ $0 + 2 }) print(mapValues) // ["second": 45, "first": 4, "three": 14, "four": 5]
- SE-0218 在
Swift 5
中新增了一個函式compactMapValues
-
compactMapValues
是將上述兩個方法的功能合併在一起, 返回一個對value
操作後的新字典, 並且自動過濾不符合條件的鍵值對
let times1 = [ "Hudson": "38", "Clarke": "42", "Robinson": "35", "Hartis": "DNF" ] let comMap2 = times1.compactMapValues({ Int($0) }) print(comMap2) // ["Clarke": 42, "Robinson": 35, "Hudson": 38]
SubSequence
-
Sequence
協議不再具有SubSequence
關聯型別。先前返回SubSequence
的Sequence
方法現在會返回具體型別 - 使用
SubSequence
的Sequence
擴充套件應該修改為類似地使用具體型別,或者修改為Collection
的擴充套件,在Collection
中SubSequence
仍然可用
// swift 5不在支援 extension Sequence { func dropTwo() -> SubSequence { return self.dropFirst(2) } } // 建議改為 extension Sequence { func dropTwo() -> DropFirstSequence<Self> { return self.dropFirst(2) } } // 或者 extension Collection { func dropTwo() -> SubSequence { return self.dropFirst(2) } }
其他相關更新
SE-0214
DictionaryLiteral
型別重新命名為 KeyValuePairs
SE-0238
- 在使用
Swift 5
軟體包管理器時,Targets
可以宣告一些常用的針對特定目標的build settings
設定 - 新設定也可以基於平臺和構建配置進行條件化處理。包含的構建設定支援
Swift
和C
語言定義,C
語言標頭檔案搜尋路徑,連結庫和連結框架
SE- 0236
- 在使用
Swift 5
時, 可以自定義所支援的最低版本號, 如果該專案的依賴包所支援的最低版本大於專案的最低版本號, 則專案會報錯
SR-695
在 Swift 5
中不再支援返回 Self
的類方法
// 不在支援 class Base { class func factory() -> Self { /*...*/ } }
SR-631
不同檔案中的副檔名無法相互識別
class FirstClass { } extension FirstClass { class SecondClass { } } // 這裡將會報錯: "SecondClass is not a member type of FirstClass" extension FirstClass.SecondClass { class ThirdClass { } }
SR-7251
在 Swift 5
中, 在所宣告的類裡面, 所宣告的變數名不能和類名一樣
struct S {} extension S { static var i: Int { return 0 } struct i {} // error: “i”的宣告無效 } // 下面的方式是沒有問題的 struct S1<T> {} extension S1 { static var i: Int { return 0 } struct i {} // This is fine! }