Skip to content

Swift 4.2 中有什么新功能?【译】

轩辕十四
Published date:
4 min read
Edit this post

好消息:Swift 4.2 现已在 Xcode 10 beta 中提供!此版本更新重要的 Swift 4.1 功能,并改进语言以准备 ABI 稳定性。

本教程介绍了 Swift 4.2 中最重要的变化。它需要 Xcode 10,因此请确保你在开始之前下载并安装 Xcode 的最新测试版。

前言


Swift 4.2 与 Swift 4.1 的源代码兼容,但与任何其他版本不兼容。 Apple 设计的 Swift 4.2 是在 Swift 5 中实现 ABI 稳定性的中间步骤,它应该能够在不同 Swift 版本编译的应用和库中保持二进制兼容。在集成到最终的 ABI 之前,ABI 功能会有大量的时间来获得来自社区的反馈。

本教程的部分包含 Swift Evolution 提案编号,如 [SE-0001] 。你可以通过点击每个提案的链接标签来浏览每个更改的详细信息。

如果你尝试了 playground 上的更改,你将从本教程中获得最大收益。启动 Xcode 10 并转到 File ▸ New ▸ Playgruond。选择 iOS 平台和 Blank 模版。把它命名为任何你喜欢的东西,然后把它保存在你想要的任何地方。你现在准备好开始了!

Note: 需要重温 Swift 4.1 亮点?查看我们的 Swift 4.1 教程:Swift 4.1 有哪些新特性?

语言的改进


这个版本中有很多语言功能的改进,例如随机数生成器,动态成员查询等等。

生成随机数字

Swift 4.1 导入 C API 来生成随机数字,如下面的代码片段所示:

let digit = Int(arc4random_uniform(10))

arc4random_uniform(_:) 返回 0 到 9 之间的随机数字。它需要你导入 Foundation 框架,并且不能工作在 Linux 上。 另一方面,所有基于 Linux 的方法都引入了模偏差(modulo bias),这意味着某些数字比其他数字更频繁地产生。

Swift 4.2 通过向标准库 [SE-0202] 添加随机API来解决这些问题:

// 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()

这是做什么的:

  1. 使用 random(in:) 从指定的范围生成随机数字。
  2. 如果范围是空的,randomElement() 返回 nil,所以需要用 if let 来处理 Int?。
  3. 可以使用 random(in:) 来产生一个随机的 Double,Float 或者 CGFloat 和 random() 来返回一个随机的 Bool。

Generating random numbers like a pro in Swift 4.2!

Swift 4.1 还使用 C 函数从数组中生成随机值:

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.1 使用 arc4random_uniform(_:) 从 playlist 生成一个有效的索引(index)并返回相应的 song。 这个解决方案需要在 Int 和 UInt32 之间转换,并且还有所有前面提到的问题。

Swift 4.2 采用更直接的方式:

if let song = playlist.randomElement() {
  print(song)
} else {
  print("Empty playlist.")
}

如果 playlist 为空 randomElement() 将返回 nil,所以需要处理返回的 String?。

Swift 4.1 没有包含任何集合混洗算法(collection shuffling algorithms),所以必须使用迂回的方式来达到预期的结果:

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

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

这是在这段代码中所做的:

  1. 可以使用 arc4random_uniform(_:) 来确定 playlist 的混排顺序,并用 sorted(_:_:) 函数返回 shuffledPlaylist 。
  2. 然后使用先前的技术将混乱的名称用 sort(_:_:) 函数排序。

Swift 4.2 提供了更高效,可以说更优雅的混洗算法:

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

在 4.2 中,只需使用 shuffled() 创建混洗播放列表(playlist)并在 shuffled() 上随机播放名称。 Boom!

Shuffling playlists has never been easier thanks to Swift 4.2!

动态成员查询

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)为下标提供点语法,而不是 [SE-0195]:

// 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"
  1. 将 Person 标记为 @dynamicMemberLookup 以启用其自定义下标的点语法。
  2. 通过为类实现 subscript(dynamicMember:),去掉用 @dynamicMemberLookup。
  3. 可以使用点语法调用先前自定义的下标。

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

动态成员查找不会混淆类属性:

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

在这种情况下,使用点语法来调用 名称 和 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."

可以使用点语法来调用汽车的下标,因为任何 Car 都是 Vehicle,并且 Vehicle 实现了 @dynamicMemberLookup 。

可以使用 protocol extensions 将动态成员查找添加到现有类型:

// 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 }
  1. 使用 @dynamicMemberLookup 注释 Random 以启用其下标的点语法。
  2. 可以通过实现 subscript(dynamicMember:) 来扩展协议并使其符合 @dynamicMemberLookup。下标使用 random(in:) 来返回 0 到 9 之间的随机数字。
  3. 扩展 Int 并使其符合 Random。
  4. 使用点语法来生成一个随机数字并从 number 中过滤出来。

枚举案例集合


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).")
}

在这里,将 Seasons 添加到 seasons,并循环访问数组以获取每个季节的名称和类型。

但 Swift 4.2 可以做的更好!

Swift 4.2 将枚举案例数组添加到枚举中 [SE-0194]:

// 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).")
}

以下是如何在 Swift 4.2 中完成同样的事情:

  1. 使 Seasons 继承 CaseIterable 以创建枚举案例。
  2. 循环浏览所有 case 并打印每个季节的名称和类型。

可以选择仅将某些情况添加到枚举案例数组中:

enum Months: CaseIterable {
  case january, february, march, april, may, june, july, august, september, october, november, december          
  
  static var allCases: [Months] {
    return [.june, .july, .august]
  }
}

在这里只添加夏季月份,因为它们是一年中最阳光的季节!

Summer is all over the place in Swift 4.2 enumerations!

如果枚举包含 unavailable 的元素,则应手动将所有 available 添加到数组中:

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, 因为这里将 .saturday 和 .sunday 标记为了 unavailable,所以在任何版本和平台都是 unavailable。

还可以将具有关联值的 case 添加到枚举 case 数组中:

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

在本例中,将网站上的所有博客类型添加到 allCases:,文章,新教程和更新。

新的序列方法(Squence Methods)

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.1 的做法是使用 first(where:) 来查找第一个青少年的年龄(ages),index(where:) 为第一个青少年的索引,index(of:) 为第一个18岁的青少年的索引 。

Swift 4.2重新命名了这些方法中的一部分以实现一致性[SE-0204]:

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.")
}

index(where:) 变成了 firstIndex(where:),而 index(of:) 变成了 firstIndex(of:) 与 first(where:) 保持一致。

Swift 4.1 也没有定义任何 Collection 方法来查找某个元素的最后一个索引或匹配给定谓词的最后一个元素。以下是 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.")
}

我们来看看这部分:

  1. 我们使用 reversed() 方法,创建了一个 ages 的翻转版本。
  2. 我们使用 first(where:) 来确定 reversedAges 中最后一名青少年的年龄,index(where:) 是最后一名青少年的索引,index(of:) 是最后一名18岁青少年的索引。

Swift 4.2 添加了相应的 Sequence 方法,将上面的方法折叠为如下形式:

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.")
}

你可以简单的使用 last(where:),lastIndex(where:) 和 lastIndex(of:) 去找到之前的元素和年龄的具体指数。

测试序列元素

Swift 4.1 中缺少一个相当简单的例程来检查序列中的所有元素是否满足特定条件。尽管如此,您仍然可以制定自己的方法,例如你必须确定所有元素是否是偶数:

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

看起来并不是很优雅,不是吗?Swift 4.2 为序列添加了这一个缺失的方法[SE-0207]:

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

看起来好多了,这简化了代码,并且具有更好的可读性。

条件一致性更新

Swift 4.2 在标准库的扩展中增加了一些条件一致性的改进[SE-0143]。

扩展中的条件一致性

Swift 4.1 无法将扩展中的 Equatable 的条件一致性综合起来。以下面的 Swift 4.1 代码片段为例:

// 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 {
  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
  1. 这里让 Tutorial 继承自 Equatable。
  2. 让 Screencast 成为通用的,因为网站作者将他们的截屏作为发布教程的基础。
  3. 对于截屏你可以实现 ==(lhs:rhs:) ,因为只要 Tutorial 符合 Equatable 那么 Screencast 也会符合 Equatable。

在 Swift 4.2 中为一个扩展添加 Equatable 的默认实现如下:

extension Screencast: Equatable where Tutorial: Equatable {}

此功能也适用于扩展中的 Hashable 和 Codable 一致性:

// 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)")
}

在这个代码块中:

  1. 让 Tutorial 继承自 Hashable 和 Codable。
  2. 如果教程这么做的话,那么也将会让 Screencast 继承自 Hashable 和 Codable。
  3. 你可以将截图添加到集合字典中并对他们进行编码。
条件一致性运行时查询

Swift 4.2 实现了条件一致性的动态查询。您可以在以下代码中看到这一点:

// 1
class Instrument {
  let brand: String
  
  init(brand: String = "") {
    self.brand = brand
  }
}

// 2
protocol Tuneable {
  func tune()
}

// 3
class Keyboard: Instrument, Tuneable {
  func tune() {
    print("\(brand) keyboard tuning.")
  }
}

// 4
extension Array: Tuneable where Element: Tuneable {
  func tune() {
    forEach { $0.tune() }
  }
}

// 5
let instrument = Instrument()
let keyboard = Keyboard(brand: "Roland")
let instruments = [instrument, keyboard]

// 6
if let keyboards = instruments as? Tuneable {
  keyboards.tune()
} else {
  print("Can't tune instrument.")
}

这里是上述的内容:

  1. 定义了一个 Instrument 类,并且他有一个成员变量 brand。
  2. 为所有可调的乐器声明了一个接口 Tuneable。
  3. 我们在 Keyboard 类中重写了 tune() 方法,并且返回键盘的标准调整。
  4. 我们使用 where 关键词来约束 Array 的元素满足 Tuneable。
  5. 我们将 Instrument 和 Keyboard 添加到 instruments 中。
  6. 如果 if 条件满足,这里会检查 instruments 是否能实现了 Tuneable,然后调用 tune()方法。在这里例子中,数组不能转化为 Tuneable,因为 Instrument 类型不满足 Tuneable。如果你创建有两个 Keyboard 元素的数组,则 if 条件会满足,并且会调用 tune() 方法。
标准库中哈希一致性改进

可选项,数组,字典和范围(range)在 Swift 4.2 中是 Hashable,当它们的元素是 Hashable 时:

struct Chord: Hashable {
  let name: String
  let description: String?
  let notes: [String]
  let signature: [String: [String]?]
  let frequency: CountableClosedRange<Int>
}

let cMajor = Chord(name: "C", description: "C major", notes: ["C", "E",  "G"], 
                   signature: ["sharp": nil,  "flat": nil], frequency: 432...446)
let aMinor = Chord(name: "Am", description: "A minor", notes: ["A", "C", "E"], 
                   signature: ["sharp": nil, "flat": nil], frequency: 440...446)
let chords: Set = [cMajor, aMinor]
let versions = [cMajor: "major", aMinor: "minor"]

这里将 cMajor 和 aMinor 放在 chords 和 versions 中。这在 Swift 4.2 之前是不可能的,因为 String?, [String], [String: [String]?] 和 CountableClosedRange<Int> 他们不满足 Hashable

哈希改进

在 Swift 4.1 中为下面的例子实现一个类的自定义散列函数:

class Country: Hashable {
  let name: String
  let capital: String
  
  init(name: String, capital: String) {
    self.name = name
    self.capital = capital
  }
  
  static func ==(lhs: Country, rhs: Country) -> Bool {
    return lhs.name == rhs.name && lhs.capital == rhs.capital
  }
  
  var hashValue: Int {
    return name.hashValue ^ capital.hashValue &* 16777619
  }
}

let france = Country(name: "France", capital: "Paris")
let germany = Country(name: "Germany", capital: "Berlin")
let countries: Set = [france, germany]
let countryGreetings = [france: "Bonjour", germany: "Guten Tag"]

由于它们是继承自 Hashable,因此您可以在这里将国家添加到集合(sets)和词典中。但是 hashValue 实现很难理解,并且对于不可信的源值不够有效。

Swift 4.2 通过定义通用哈希函数修复了这个问题[SE-0206]:

class Country: Hashable {
  let name: String
  let capital: String
  
  init(name: String, capital: String) {
    self.name = name
    self.capital = capital
  }
  
  static func ==(lhs: Country, rhs: Country) -> Bool {
    return lhs.name == rhs.name && lhs.capital == rhs.capital
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(name)
    hasher.combine(capital)
  }
}

在这里,用 hash(into:) 函数替换了 Country 中原来的 hashValue。该函数使用 combine() 将类属性提供给 hasher。这很容易实现,它提高了以前所有版本的性能。

Hashing sets and dictionaries like a pro in Swift 4.2!

从集合中删除元素

我们通常会想要从集合中删除所有出现的特定元素。在 Swift 4.1 中我们可以使用 filter(_:) 来实现:

var greetings = ["Hello", "Hi", "Goodbye", "Bye"]
greetings = greetings.filter { $0.count <= 3 }

这里过滤 greetings 中的元素,只返回元素字符数小于或等于 3 的元素。这并不影响原始数组。所以你不得不把它再赋值给 greetings。

Swift 4.2 添加了 removeAll(_:) 方法[SE-0197]:

greetings.removeAll { $0.count > 3 }

这里将执行移除操作。再一次,我们又简化了代码并且增加了可读性。

切换布尔值状态

切换布尔值状态!谁在 Swift 4.1 中没有这样用过:

extension Bool {
  mutating func toggle() {
    self = !self
  }
}

var isOn = true
isOn.toggle()

Swift 4.2 为 Bool 添加了 toggle() 函数 [SE-0199]:

新的编译指令

Swift 4.2 定义了一些编译器指令,用于在代码中标识出问题[SE-0196]:

// 1
#warning("There are shorter implementations out there.")

let numbers = [1, 2, 3, 4, 5]
var sum = 0
for number in numbers {
  sum += number
}
print(sum)

// 2
#error("Please fill in your credentials.")

let username = ""
let password = ""
switch (username.filter { $0 != " " }, password.filter { $0 != " " }) {
  case ("", ""):
    print("Invalid username and password.")
  case ("", _):
    print("Invalid username.")
  case (_, ""):
    print("Invalid password.")
  case (_, _):
    print("Logged in succesfully.")
}     

这里是如何工作的:

  1. 这里用 #warning 来进行一个提醒,在 numbers 中添加元素的功能方法比命令式方法短。
  2. 用 #error 来让其他开发者在登陆之前输入用户名和密码。

新的指针功能

withUnsafeBytes(of:_:) 和 withUnsafePointer(to:_:) 只适用于 Swift 4.1 中的可变变量:

let value = 10
var copy = value
withUnsafeBytes(of: &copy) { pointer in print(pointer.count) }
withUnsafePointer(to: &copy) { pointer in print(pointer.hashValue) }

所以我们必须创建一个值的副本(copy of value)才能使这两个函数正常工作。 Swift 4.2 为常量重载这些函数,所以你不再需要保存它们的值[SE-0205]:

withUnsafeBytes(of: value) { pointer in print(pointer.count) }
withUnsafePointer(to: value) { pointer in print(pointer.hashValue) }

内存布局更新

Swift 4.2 使用键路径查询存储属性的内存布局[SE-0210]。以下是它的工作原理:

// 1
struct Point {
  var x, y: Double
}

// 2
struct Circle {
  var center: Point
  var radius: Double
  
  var circumference: Double {
    return 2 * .pi * radius
  }
  
  var area: Double {
    return .pi * radius * radius
  }
}

// 3
if let xOffset = MemoryLayout.offset(of: \Circle.center.x), 
   let yOffset = MemoryLayout.offset(of: \Circle.center.y), 
   let radiusOffset = MemoryLayout.offset(of: \Circle.radius) {
  print("\(xOffset) \(yOffset) \(radiusOffset)")
} else {
  print("Nil offset values.")
}

// 4
if let circumferenceOffset = MemoryLayout.offset(of: \Circle.circumference), 
   let areaOffset = MemoryLayout.offset(of: \Circle.area) {
  print("\(circumferenceOffset) \(areaOffset)")
} else {
  print("Nil offset values.")
}

我们来一步一步的解析:

  1. 我们定义了该点的水平和垂直坐标。
  2. 我们声明了一个圆的中心,圆周,面积和半径。
  3. 我们使用键路径来获取圆的存储属性的偏移量。
  4. 由于它们不是以内联方式存储的,因此我们将返回nil作为圆的计算属性的偏移量。

模块中的内联函数

在 Swift 4.1 中我们不能在我们的模型中声明内联函数。点击 View ▸ Navigators ▸ Show Project Navigator,右键点击 Source 并且选择 新建文件。重命名为 FactorialKit.swift 然后用下面的代码来替换它的内容:

public class CustomFactorial {
  private let customDecrement: Bool
  
  public init(_ customDecrement: Bool = false) {
    self.customDecrement = customDecrement
  }
  
  private var randomDecrement: Int {
    return arc4random_uniform(2) == 0 ? 2 : 3
  }
  
  public func factorial(_ n: Int) -> Int {
    guard n > 1 else {
      return 1
    }
    let decrement = customDecrement ? randomDecrement : 1
    return n * factorial(n - decrement)
  }
}

我们已经创建了阶乘实现的自定义版本。切换回 playground 并在底部添加以下代码:

let standard = CustomFactorial()
standard.factorial(5)
let custom = CustomFactorial(true)
custom.factorial(5)

这里,生成默认的阶乘和随机的阶乘。跨模块函数在 Swift 4.2[SE-0193]中内嵌时更有效率,因此将 FactorialKit.swift 中的代码用以下方法替换 CustomFactorial:

public class CustomFactorial {
  @usableFromInline let customDecrement: Bool
  
  public init(_ customDecrement: Bool = false) {
    self.customDecrement = customDecrement
  }
  
  @usableFromInline var randomDecrement: Int {
    return Bool.random() ? 2 : 3
  }
  
  @inlinable public func factorial(_ n: Int) -> Int {
    guard n > 1 else {
      return 1
    }
    let decrement = customDecrement ? randomDecrement : 1
    return n * factorial(n - decrement)
  }
}

这里我们做了这些:

  1. 将 customDecrement 和 randomDecrement 都设置为 internal,并将它们标记为 @usableFromInline,因为我们在内联因子实现中使用它们。
  2. 我们用 @inlinable 注释 factorial(_:) 以使其成为内联。这是可以的,因为我们声明这个函数是 public。

其他的变化


这里还有一些你应该知道的 Swift 4.2 的一些其他变化。

Swift 的包管理器更新

Swift 4.2 为包管理加入了一些改进。

定义包的 Swift 语言版本

Swift 4.1 在 Package.swift 文件中,定义了一个 [Int] 类型的 swiftLanguageVersions 变量。所以你可以只声明你的包的主要版本:

let package = Package(name: "Package", swiftLanguageVersions: [4])

Swift 4.2 可以通过 SwiftVersion枚举定义小版本[SE-0209]:

let package = Package(name: "Package", swiftLanguageVersions: [.v4_2])

我们还可以使用 .version(_:) 声明未来版本:

let package = Package(name: "Package", swiftLanguageVersions: [.version("5")])
声明包的本地依赖关系

在 Swift 4.1 中,使用存储库链接声明了包的依赖关系。如果有互连的包这将增加开销,所以 Swift 4.2 在这种情况下使用本地路径代替[SE-0201]。

将系统库目标(system library targets)添加到包中

System-module packages 在 Swift 4.1 中需要单独的存储库。这使得包管理器难以使用,因此 Swift 4.2 将它们替换为 system library targets[SE-0208]。

删除隐式解包的选项

在 Swift 4.1 中,可以在嵌套类型中使用隐式解包:

let favoriteNumbers: [Int!] = [10, nil, 7, nil]
let favoriteSongs: [String: [String]!] = ["Cosmin": ["Nothing Else Matters", "Stairway to Heaven"], 
                                          "Oana": nil] 
let credentials: (usermame: String!, password: String!) = ("Cosmin", nil)

Swift 4.2 将它们从数组,字典和元组中删除[SE-0054]:

let favoriteNumbers: [Int?] = [10, nil, 7, nil]
let favoriteSongs: [String: [String]?] = ["Cosmin": ["Nothing Else Matters", "Stairway to Heaven"], 
                                          "Oana": nil] 
let credentials: (usermame: String?, password: String?) = ("Cosmin", nil)

原文链接


Previous
在正确的线程上使用Observable【译】
Next
在 macOS 中用命令格式化 APFS 格式 U 盘