Swift 4.2 新特性(译)

Posted 崔小花o

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 4.2 新特性(译)相关的知识,希望对你有一定的参考价值。

一、概述

Swift 4.2 在 Xcode 10 beta 版上可以使用了,在 Swift 4.1 的基础上更新了很多语言特性,为 Swift 5 中 ABI 稳定做好准备。

这篇文章包含了 Swift 4.2 中的重大的改变。因为 Swift 4.2 需要 Xcode 10,所以请下载安装最新的 Xcode 测试版本。

二、准备

Swift 4.2 和 Swift 4.1 源码兼容,但是和其他发布版本的二进制不兼容。Swift 4.2 是 Swift 5 实现 ABI 稳定(不同的 Swift 版本编译的应用程序和库之间实现兼容)的一个中间阶段。ABI 的特性在集成进最终的 ABI 之前会接收社区的大量反馈。

三、语言演进

在这个版本中有很多新的语言特性。例如,随机数生成,动态成员查找等等

3.1 随机数生成

3.1.1 随机数生成
arc4random_uniform(_: ) 返回一个 0 - 9 之间的随机数字。这种实现方式有两个问题:

  • 需要引入 Foundation 框架,在 Linux 下无法工作。
  • Linux上的随机数生成会产生模偏差(有取模的过程,更容易随机到小的数)。
// Swift 4.1
let digit = Int(arc4random_uniform(10))

Swift 4.2 在标准库中添加了随机数的 API SE-0202

/// Swift 4.2
// 1  
let digit = Int.random(in: 0..<10)// 2
if let anotherDigit = (0..<10).randomElement() 
 print(anotherDigit)
 else 
 print("Empty range.")
// 3
let double = Double.random(in: 0..<1)
let float = Float.random(in: 0..<1)
let cgFloat = CGFloat.random(in: 0..<1)
let bool = Bool.random()

注:randomElement() 如果 range 是空,返回 nil

3.1.2 数组随机

Swift 4.1 数组随机也是采用 C 函数的形式,这种方式会存在上面提到的问题,而且会存在 Int 和 Int32 转换的问题。

/// swift 4.1
let playlist = ["Nothing Else Matters", "Stairway to Heaven", "I Want to Break Free", "Yesterday"]
let index = Int(arc4random_uniform(UInt32(playlist.count)))
let song = playlist[index]

Swift 4.2 采用了更加简单直接的方式。

/// swift 4.2
if let song = playlist.randomElement() 
 print(song)
 else 
 print("Empty playlist.")

3.1.3 洗牌算法
Swift 4.1 不包含任何集合的洗牌算法,所以要采用比较曲折的方式来实现。

// 1
let shuffledPlaylist = playlist.sorted _, _ in arc4random_uniform(2) == 0 

// 2
var names = ["Cosmin", "Oana", "Sclip", "Nori"]
names.sort  _, _ in arc4random_uniform(2) == 0 

Swift 4.2 提供了更加高效更加优雅的实现 Shuffling Algorithms

let shuffledPlaylist = playlist.shuffled()
names.shuffle()

注:使用 shuffled() 来创建一个洗牌后的数组。使用 shuffle() 来将数组洗牌。

3.2 动态成员查找

Swift 4.1 使用下面的方式实现自定义下标操作。

/// swift 4.1
class Person 
  let name: String
  let age: Int
  private let details: [String: String]
  
  init(name: String, age: Int, details: [String: String]) 
    self.name = name
    self.age = age
    self.details = details
  
  
  subscript(key: String) -> String 
    switch key 
      case "info":
        return "\\(name) is \\(age) years old."
      default:
        return details[key] ?? ""
    
  


let details = ["title": "Author", "instrument": "Guitar"]
let me = Person(name: "Cosmin", age: 32, details: details)
me["info"]   // "Cosmin is 32 years old."
me["title"]  // "Author"

Swift 4.2 使用动态成员查找来提供点语法来实现下标调用 Dynamic Member Lookup

/// swift 4.2
// 1
@dynamicMemberLookup
class Person 
  let name: String
  let age: Int
  private let details: [String: String]
  
  init(name: String, age: Int, details: [String: String]) 
    self.name = name
    self.age = age
    self.details = details
  
  
  // 2
  subscript(dynamicMember key: String) -> String 
    switch key 
      case "info":
        return "\\(name) is \\(age) years old."
      default:
        return details[key] ?? ""
    
  


// 3
me.info   // "Cosmin is 32 years old." 
me.title  // "Author"

使用步骤:

  • 标记 Person 为 @dynamicMemberLookup 使下标可以使用点语法
  • 遵守 @dynamicMemberLookup 实现 subscript(dynamicMember:) 方法
  • 使用点语法调用之前定义的下标

注:编译器会在运行时动态评估下标的调用,这样就可以写出像 Python 或者 Ruby 等脚本语言一样类型安全的代码。

动态成员查找不会和类的属性混淆。

me.name // "Cosmin"
me.age // 32

可以使用点语法而非下标来调用 name 和 age。而且派生类可以继承基类的动态成员查找。

@dynamicMemberLookup
class Vehicle 
  let brand: String
  let year: Int
  
  init(brand: String, year: Int) 
    self.brand = brand
    self.year = year
  
  
  subscript(dynamicMember key: String) -> String 
    return "\\(brand) made in \\(year)."
  


class Car: Vehicle 

let car = Car(brand: "BMW", year: 2018)
car.info  // "BMW made in 2018."

可以通过协议拓展给已有类型添加动态成员查找

// 1
@dynamicMemberLookup
protocol Random 

// 2
extension Random 
  subscript(dynamicMember key: String) -> Int 
    return Int.random(in: 0..<10)
  


// 3
extension Int: Random 

// 4
let number = 10
let randomDigit = String(number.digit)
let noRandomDigit = String(number).filter  String($0) != randomDigit 

3.3 枚举实例集合

Swift 4.1 默认没有提供访问枚举实例集合的方式,所以实现方式不是很优雅。

enum Seasons: String 
  case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"


enum SeasonType 
  case equinox
  case solstice


let seasons = [Seasons.spring, .summer, .autumn, .winter]
for (index, season) in seasons.enumerated() 
  let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
  print("\\(season.rawValue) \\(seasonType).")

为了解决这个问题,Swift 4.2 给枚举类型添加了实例数组。

// 1
enum Seasons: String, CaseIterable 
  case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"


enum SeasonType 
  case equinox
  case solstice


// 2
for (index, season) in Seasons.allCases.enumerated() 
  let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
  print("\\(season.rawValue) \\(seasonType).")

如果枚举中包含 unavailable,需要将availablecase手动维护协议中的 allCases

enum Days: CaseIterable 
  case monday, tuesday, wednesday, thursday, friday
  
  @available(*, unavailable)
  case saturday, sunday
  
  static var allCases: [Days] 
    return [.monday, .tuesday, .wednesday, .thursday, .friday]
  

allCases 中只能添加 weekdays,因为 saturdaysunday 被标记为各个平台不可用。

枚举实例数组中也可以添加有关联值的实例。

enum BlogPost: CaseIterable 
  case article
  case tutorial(updated: Bool)
  
  static var allCases: [BlogPost] 
    return [.article, .tutorial(updated: true), .tutorial(updated: false)]
  

3.4 新的序列方法

Swift 4.1 中的 Sequence 定义了查找指定元素的第一个索引位置或者满足指定条件的第一个元素的方法。

/// swift 4.1
let ages = ["ten", "twelve", "thirteen", "nineteen", "eighteen", "seventeen", "fourteen",  "eighteen", "fifteen", "sixteen", "eleven"]

if let firstTeen = ages.first(where:  $0.hasSuffix("teen") ), 
   let firstIndex = ages.index(where:  $0.hasSuffix("teen") ), 
   let firstMajorIndex = ages.index(of: "eighteen") 
  print("Teenager number \\(firstIndex + 1) is \\(firstTeen) years old.")
  print("Teenager number \\(firstMajorIndex + 1) isn't a minor anymore.")
 else 
  print("No teenagers around here.")

Swift 4.2 为了实现一致性重构了方法名

/// swift 4.2
if let firstTeen = ages.first(where:  $0.hasSuffix("teen") ), 
   let firstIndex = ages.firstIndex(where:  $0.hasSuffix("teen") ), 
   let firstMajorIndex = ages.firstIndex(of:  "eighteen") 
  print("Teenager number \\(firstIndex + 1) is \\(firstTeen) years old.")
  print("Teenager number \\(firstMajorIndex + 1) isn't a minor anymore.")
 else 
  print("No teenagers around here.")

Swift 4.1 也没有定义查找指定元素的最后一个索引的位置和满足指定条件的的最后一个元素等方法。在 Swift 4.1 中我们可能采用下面的方法来处理。

/// swift 4.1
// 1
let reversedAges = ages.reversed()

// 2
if let lastTeen = reversedAges.first(where:  $0.hasSuffix("teen") ), 
   let lastIndex = reversedAges.index(where:  $0.hasSuffix("teen") )?.base, 
   let lastMajorIndex = reversedAges.index(of: "eighteen")?.base 
  print("Teenager number \\(lastIndex) is \\(lastTeen) years old.")
  print("Teenager number \\(lastMajorIndex) isn't a minor anymore.")
 else 
  print("No teenagers around here.")


Swift 4.2 添加了相应的方法,使用方式如下

if let lastTeen = ages.last(where:  $0.hasSuffix("teen") ), 
   let lastIndex = ages.lastIndex(where:  $0.hasSuffix("teen") ), 
   let lastMajorIndex = ages.lastIndex(of: "eighteen") 
  print("Teenager number \\(lastIndex + 1) is \\(lastTeen) years old.")
  print("Teenager number \\(lastMajorIndex + 1) isn't a minor anymore.")
 else 
  print("No teenagers around here.")


3.5 检测序列元素

Swift 4.1 中没有检查序列中所有元素是否满足某个指定条件的方法。不过你可以实现你自己的方法,例如下面检测集合中的元素是否都是偶数。

let values = [10, 8, 12, 20]
let allEven = !values.contains  $0 % 2 == 1 

Swift 4.2 添加了新的方法,很好的简化了代码,提升了可读性。

let allEven = values.allSatisfy  $0 % 2 == 0 

3.6 条件遵守更新

Swift 4.2 给拓展和标准库中添加一些条件遵守方面的改进。

3.6.1 拓展中的条件遵守

Swift 4.1 不能在拓展中自动合成 Equatable 的协议实现。例子如下:

// 1
struct Tutorial : Equatable 
  let title: String
  let author: String


// 2
struct Screencast<Tutorial> 
  let author: String
  let tutorial: Tutorial


// 3 
extension Screencast: Equatable where Tutorial: Equatable 
  // 必须自己实现 == 方法,Swift 4.1 不会自动合成
  static func ==(lhs: Screencast, rhs: Screencast) -> Bool 
    return lhs.author == rhs.author && lhs.tutorial == rhs.tutorial
  


// 4
let swift41Tutorial = Tutorial(title: "What's New in Swift 4.1?", author: "Cosmin Pupăză")
let swift42Tutorial = Tutorial(title: "What's New In Swift 4.2?", author: "Cosmin Pupăză")
let swift41Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift41Tutorial)
let swift42Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift42Tutorial)
let sameScreencast = swift41Screencast == swift42Screencast

Swift 4.2 只需要遵守协议,不需要实现。因为编译器会添加一个默认的Equatable协议实现。

extension Screencast: Equatable where Tutorial: Equatable 

这个特性也同样支持 HashableCodable

// 1
struct Tutorial: Hashable, Codable 
  let title: String
  let author: String


struct Screencast<Tutorial> 
  let author: String
  let tutorial: Tutorial


// 2
extension Screencast: Hashable where Tutorial: Hashable 
extension Screencast: Codable where Tutorial: Codable 

// 3
let screencastsSet: Set = [swift41Screencast, swift42Screencast]
let screencastsDictionary = [swift41Screencast: "Swift 4.1", swift42Screencast: "Swift 4.2"]

let screencasts = [swift41Screencast, swift42Screencast]
let encoder = JSONEncoder()
do 
  try encoder.encode(screencasts)
 catch 
  print("\\(error)")

3.6.2 条件遵守运行时查询

Swift 4.2 实现条件遵守的动态查询。可以从下面的例子看出。

// 1
class Instrument 
  let brand: String
  
  init(brand: String = "") 
    self.brand = brand
  [译] 那些好玩却尚未被 ECMAScript 2017 采纳的提案

javascript新特性

前端周报:W3C 收到 CSS 工作组提案;深入探索 iOS 漏洞利用链;Angular 9 的新特性;

译ECMAScript 2016, 2017, 2018 新特性之必读篇

ECMAScript 2022 会有哪些新特性?

深度干货 | MongoDB 4.2新特性解读