Swift5.0新特性更新

RunTitan發表於2019-01-28

Swift

  • 原文部落格地址: Swift 5.0新特性更新
  • 期待已久的Swift 5.0終於來啦, Swift 5.0Swift中最備受關注的一個版本, 傳說中ABI穩定的版本
  • 隨著Xcode Bate 10.2的釋出, Swift 5.0也釋出了測試版, 相信也帶來了很多優化和改進
  • 下面執行環境都是在Xcode Bate 10.2環境中進行的

新特性

dynamicCallable

  • SE-0216
  • @dynamicCallableSwift新增了一個新屬性, 允許使用一個簡單的語法糖呼叫函式一樣呼叫命名型別
  • 如果需要新增@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的版本有兩個函式compactMapmapValues
  • 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-0218Swift 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關聯型別。先前返回SubSequenceSequence方法現在會返回具體型別
  • 使用SubSequenceSequence擴充套件應該修改為類似地使用具體型別,或者修改為Collection的擴充套件,在CollectionSubSequence仍然可用
// 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設定
  • 新設定也可以基於平臺和構建配置進行條件化處理。包含的構建設定支援SwiftC語言定義,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!
}
複製程式碼

參考文獻