Swift 5新特性

作者: 纯情_小火鸡 | 来源:发表于2019-04-01 10:01 被阅读0次

    在Xcode10.2终于可以用上Swift5了,这次发布带来了ABI(应用程序机器二元码界面(英语:application binary interface,缩写为ABI)是指两程

    序模块间的接口;通常其中一个程序模块会是库或操作系统所提供的服务,而另一边的模块则是用户所运行的程序。)的稳定性以及一些期待已久的新特性。

    Swift 5是与Swift 4.2是源代码兼容的,但与早期的Swift版本二进制不兼容。不过,由于ABI的稳定性,未来的版本将兼容Swift 5。

    整数的倍数

    在4.2中,用余数运算符确定一个数字是否是另一个数字的倍数需要这么做:

    let firstNumber = 4
    let secondNumber = 2
    //你需要确保secondNumber不会是0,不然%会抛出异常
    if secondNumber != 0 && firstNumber % secondNumber == 0 {
      print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
    }
    

    但是在Swift5中可以这么写,即使传入的参数是0:

    if firstNumber.isMultiple(of: secondNumber) {
      print("\(secondNumber) * \(firstNumber / secondNumber) = \(firstNumber)")
    }
    

    转义字符

    Swift 4.2在字符串中使用反斜杠和引号来表达转义序列:

    let escape = "You use escape sequences for \"quotes\"\\\"backslashes\" in Swift 4.2."
    let multiline = """
                    You use escape sequences for \"\"\"quotes\"\"\"\\\"\"\"backslashes\"\"\"
                    on multiple lines
                    in Swift 4.2.
                    """
    

    Swift 5添加了原始字符串。您可以在字符串的开头和结尾添加#,这样您就可以使用反斜杠和引号而不会出现问题:

    let raw = #"You can create "raw"\"plain" strings in Swift 5."#
    let multiline = #"""
                    You can create """raw"""\"""plain""" strings
                    on multiple lines
                    in Swift 5.
                    """#
    let hashtag = ##"You can use the Swift "hashtag" #swift in Swift 5."##
    

    新字符属性

    在4.2中,如果需要计算字符串中数字的个数:

    let id = "ID10"
    var digits = 0
    id.forEach { digits += Int(String($0)) != nil ? 1 : 0 }
    print("Id has \(digits) digits.")
    

    Swift5可以这么写:

    id.forEach { digits += $0.isNumber ? 1 : 0 }
    

    新的计算Unicode数量属性

    4中如果需要计算字符串中unicode的字母数量,需要通过检查每个字符的unicode标量是表示小写字母还是大写字母,计算username有多少个字母。

    let username = "bond007"
    var letters = 0
    username.unicodeScalars.forEach { 
      letters += (65...90) ~= $0.value || (97...122) ~= $0.value ? 1 : 0
    }
    print("Username has \(letters) letters.")
    

    在Swift5可以这样写:

    username.unicodeScalars.forEach { letters += $0.properties.isAlphabetic ? 1 : 0 }
    

    移除子序列

    Swift 4.2从序列移除子序列:

    extension Sequence {
      func remove(_ s: String) -> SubSequence {
        guard let n = Int(s) else {
          return dropLast()
        }
        return dropLast(n)
      }
    }
    
    let sequence = [5, 2, 7, 4]
    sequence.remove("2") // [5, 2]
    sequence.remove("two") // [5, 2, 7]
    

    在本例中,如果s是Int或最后一个元素,则remove(_:)从序列中删除最后n个元素。

    Swift 5用序列中的具体类型替换子序列:

    extension Sequence {
      func remove(_ s: String) -> [Element] {
        guard let n = Int(s) else {
          return dropLast()
        }
        return dropLast(n)
      }
    }
    

    字典比较

    Swift 4.2使用mapValues, filter和reduce从字典中过滤nil:

    let students = ["Oana": "10", "Nori": "ten"]
    let filterStudents = students.mapValues(Int.init)
      .filter { $0.value != nil }
      .mapValues { $0! }
    let reduceStudents = students.reduce(into: [:]) { $0[$1.key] = Int($1.value) }
    

    这段代码使用带有filter或reduce的map值来从学生中确定成绩有效的学生。这两种方法都需要多个字典传递,并使代码复杂化。

    Swift5可以这样写,一行代码搞定:

    let mapStudents = students.compactMapValues(Int.init)
    

    数值协议更新

    4中对象如何想要实现数值运算:

    // 1
    struct Vector {
      let x, y: Int
      
      init(_ x: Int, _ y: Int) {
        self.x = x
        self.y = y
      }
    }
    
    // 2
    extension Vector: ExpressibleByIntegerLiteral {
      init(integerLiteral value: Int) {
        x = value
        y = value
      }
    }
    
    // 3
    extension Vector: Numeric {
      var magnitude: Int {
        return Int(sqrt(Double(x * x + y * y)))
      }  
    
      init?<T>(exactly value: T) {
        x = value as! Int
        y = value as! Int
      }
      
      static func +(lhs: Vector, rhs: Vector) -> Vector {
        return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
      }
      
      static func +=(lhs: inout Vector, rhs: Vector) {
        lhs = lhs + rhs
      }
      
      static func -(lhs: Vector, rhs: Vector) -> Vector {
        return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
      }
      
      static func -=(lhs: inout Vector, rhs: Vector) {
        lhs = lhs - rhs
      }
      
      static func *(lhs: Vector, rhs: Vector) -> Vector {
        return Vector(lhs.x * rhs.y, lhs.y * rhs.x)
      }
      
      static func *=(lhs: inout Vector, rhs: Vector) {
        lhs = lhs * rhs
      }
    }
    
    // 4
    extension Vector: CustomStringConvertible {
      var description: String {
        return "(\(x) \(y))"
      }
    }
    

    然而在Swift5中只需要实现向量的AdditiveArithmetic协议:

    extension Vector: AdditiveArithmetic {
      static var zero: Vector {
        return Vector(0, 0)
      }
      
      static func +(lhs: Vector, rhs: Vector) -> Vector {
        return Vector(lhs.x + rhs.x, lhs.y + rhs.y)
      }
      
      static func +=(lhs: inout Vector, rhs: Vector) {
        lhs = lhs + rhs
      }
      
      static func -(lhs: Vector, rhs: Vector) -> Vector {
        return Vector(lhs.x - rhs.x, lhs.y - rhs.y)
      }
      
      static func -=(lhs: inout Vector, rhs: Vector) {
        lhs = lhs - rhs
      }
    }
    

    枚举

    Swift 4.2不能正确处理新的枚举,在下面代码中如果新增情况下,是执行default:

    // 1
    enum Post {
      case tutorial, article, screencast, course
    }
    
    // 2
    func readPost(_ post: Post) -> String {
      switch post {
        case .tutorial:
          return "You are reading a tutorial."
        case .article:
          return "You are reading an article."
        default:
          return "You are watching a video."
      }
    }
    
    // 3
    let screencast = Post.screencast
    readPost(screencast) // "You are watching a video."
    let course = Post.course
    readPost(course) // "You are watching a video."
    

    然而Swift 5会增加枚举用例:

    func readPost(_ post: BlogPost) -> String {
      switch post {
        case .tutorial:
          return "You are reading a tutorial."
        case .article:
          return "You are reading an article."
        @unknown default:
          return "You are reading a blog post."
      }
    }
    
    readPost(screencast) // "You are reading a blog post."
    readPost(course) // "You are reading a blog post."
    readPost(podcast) // "You are reading a blog post."
    

    在这段代码中,只需要将默认值标记为@unknown, Swift就会警告你这个切换并不彻底。

    标准库新增Result

    // 1
    enum ConnectionError: Error {
      case noNetwork, noDatabase
    }
    
    // 2
    let networkSuccess = Result<String, ConnectionError>.success("Network connected!")
    let databaseSuccess = Result<String, ConnectionError>.success("Database connected!")
    let networkFailure = Result<String, ConnectionError>.failure(.noNetwork)
    let databaseFailure = Result<String, ConnectionError>.failure(.noDatabase)
    let sameSuccess = networkSuccess == databaseSuccess
    let sameFailure = networkFailure == databaseFailure
    let success: Set = [networkSuccess, databaseSuccess]
    let failure: Set = [networkFailure, databaseFailure]
    let successDictionary = [
      networkSuccess: try! networkSuccess.get(),
      databaseSuccess: try! databaseSuccess.get()
    ]
    let failureDictionary = [
      networkFailure: ConnectionError.noNetwork,
      databaseFailure: ConnectionError.noDatabase
    

    Conforming Never to Equatable and Hashable

    let alwaysSucceeds = Result<String, Never>.success("Network connected!")
    let neverFails = Result<String, Never>.success("Database connected!")
    let alwaysFails = Result<Never, ConnectionError>.failure(.noNetwork)
    let neverSucceeds = Result<Never, ConnectionError>.failure(.noDatabase)
    let sameValue = alwaysSucceeds == neverFails
    let sameError = alwaysFails == neverSucceeds
    let alwaysSuccess: Set = [alwaysSucceeds, neverFails]
    let alwaysFailure: Set = [alwaysFails, neverSucceeds]
    let alwaysSuccessDictionary = [
      alwaysSucceeds: try! alwaysSucceeds.get(),
      neverFails: try! neverFails.get()
    ]
    let alwaysFailureDictionary = [
      alwaysFails: ConnectionError.noNetwork,
      neverSucceeds: ConnectionError.noDatabase
    ]
    

    Dynamically Callable Types

    Swift 5定义了与Python或Ruby等脚本语言互操作的动态可调用类型

    // 1
    @dynamicCallable
    class DynamicFeatures {
      // 2
      func dynamicallyCall(withArguments params: [Int]) -> Int? {
        guard !params.isEmpty else {
          return nil
        }
        return params.reduce(0, +)
      }
      
      func dynamicallyCall(withKeywordArguments params: KeyValuePairs<String, Int>) -> Int? {
        guard !params.isEmpty else {
          return nil
        }
        return params.reduce(0) { $1.key.isEmpty ? $0 : $0 + $1.value }
      }
    }
    
    // 3
    let features = DynamicFeatures()
    features() // nil
    features(3, 4, 5) // 12
    features(first: 3, 4, second: 5) // 8
    

    实现说明:

    1. 在DynamicFeatures中声名@dynamicCallable为动态调用类型
    2. 实现该协议的 dynamicallyCall(withArguments:) & dynamicallyCall(withKeywordArguments:)方法
    3. 正常调用features,编译器会自动调用内部方法实现

    降维嵌套可选

    如果我们在4.2中使用try?,division会变成恶心的 Int??,然后需要我们解包两次:

    extension Int {
      // 1
      enum DivisionError: Error {
        case divisionByZero
      }
      
      // 2
      func divideBy(_ number: Int) throws -> Int {
        guard number != 0 else {
          throw DivisionError.divisionByZero
        }
        return self / number
      }
    }
    
    // 3
    let number: Int? = 10
    let division = try? number?.divideBy(2)
    if let division = division, 
       let final = division {
      print(final)
    }
    

    Swift5却不需要这样了,try?不再会创建嵌套可选链,所以只需要解包一次就好:

    if let division = division {
      print(division)
    }
    

    统一Key Paths

    在4.2中,可以使用.self取获取和更新值:

    class Tutorial {
      let title: String
      let author: String
      init(title: String, author: String) {
        self.title = title
        self.author = author
      }
    }
    
    var tutorial = Tutorial(title: "What's New in Swift 5.0?", author: "Cosmin Pupaza")
    tutorial.self = Tutorial(title: "What's New in Swift 5?", author: "Cosmin Pupăză")
    

    Swift5中新增了通过identity key paths方式使用 \.self 获取设置该对象:

    tutorial[keyPath: \.self] = Tutorial(
      title: "What's New in Swift 5?",
      author: "Cosmin Pupăză")
    

    枚举的可变参数

    在4.2中如果枚举为可变参数是这么写的:

    enum BlogPost {
      case tutorial(_: String...)
      case article(_: String...)
    }
    

    在Swift5中需要使用Array替换:

    enum BlogPost {
      case tutorial([String])
      case article([String])
    }
    

    废弃String Index Encoded Offsets

    在Swift4.2中使用UTF-16编码,调用 encodedOffset 会返回UTF-16字符串的偏移量:

    let swiftVersion = "Swift 4.2"
    let offset = swiftVersion.endIndex.encodedOffset
    

    但是在Swift5中,如果使用UTF-8时这个方法是不管用的,使用utf16Offset(in:)替换:

    let swiftVersion = "Swift 5"
    let offset = swiftVersion.endIndex.utf16Offset(in: swiftVersion)
    

    写在最后 : Swift 5在Swift 4.2的基础上增加了很多新特性,也使得ABI更加稳定。这是发展过程中的一个重要里程碑,从现在开始将会有更少的变化。Swift CHANGELOG & Swift standard library diffs 可以获取更多细节。

    相关文章

      网友评论

        本文标题:Swift 5新特性

        本文链接:https://www.haomeiwen.com/subject/ddembqtx.html