Swift 5 新特性

概覽

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)定

  1. 什么是ABI。
    在運行時,Swift程序二進制文件通過ABI與其他庫和組件交互,ABI定義了很多底層細節(jié):如何調(diào)用函數(shù),如何在內(nèi)存中表示數(shù)據(jù),甚至是元數(shù)據(jù)的位置以及如何訪問它。
  2. 什么是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)兼容。
  3. ABI穩(wěn)定帶來的便利
    • 因為源代碼兼容,開發(fā)者能夠跨多個Swift版本維護單個代碼庫;
    • Swift應(yīng)用程序包會變得更小;
    • Swift語言變化以及變化頻率都會有所下降;
  4. 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-0230try?返回的多層 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ù)compactMapmapValue結(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]

參看文獻:

  1. swift-evolution
  2. Swift5.0新特性
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容