作者 | Paul Hudson
来源 | Hacking with Swift
来源公众号丨知识小集(zsxjtip)
随着 Xcode 11.4 的发布,Swift 5.2 也正式到来,新的版本包含少量语言层面的更新,代码大小和内存使用的减少,以及新的诊断体系结构。新的诊断体系结构可以帮助我们更快地理解和解决错误。
在本文中,将通过具体的实例来说明 Swift 5.2 中的一些新特性,以便让您对这些更新有一个更清晰的认识。我建议你通过链接到对应的 Swift Evolution 中,可获取更多信息。
将 Key Path 表达式作为函数
SE-0249 介绍了一个奇特的快捷方式,让我们可以在一些特定情况下以函数调用的方式使用 keypath。
这个 Evolution 提案提议在使用类型为 (Root) -> Value
的函数的地方,可以使用更简便的 \Root.value
来代替,也就是说如果将一个 Car
类型实例作为参数传递给方法,然后返回这个 Car 实例的 licensePlate 属性值,则现在可以直接使用 Car.licensePlate
来代替方法调用。
让我们举个例子,这里有一个 User
类型,定义了 4 个属性:
struct User {
let name: String
let age: Int
let bestFriend: String?
var canVote: Bool {
age >= 18
}
}
我们可以创建该结构的一些实例并将其放入数组,如下所示:
let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]
我们的目标是获取所有用户的 name 值的数组,那么可以使用类似以下的 key path 来实现:
let userNames = users.map(\.name)
print(userNames)
在此之前,我们必须通过闭包来手动获取 name 值,即
let oldUserNames = users.map { $0.name }
在其它地方也可以使用同样的方法,即以类型实例为参数,并返回其某一属性值的任何地方,都可以使用这种 key path。例如,以下代码返回所有可以投票的用户:
let voters = users.filter(\.canVote)
而以下代码则返回 bestFriend 不为空的用户的好友:
let bestFriends = users.compactMap(\.bestFriend)
可调用类型
SE-0253 在 Swift 中引入了可静态调用的值,这是一种有趣的说法,即如果值的类型实现了一个名为 callAsFunction()
的方法,则现在可以直接以函数的方式来调用该类型的值。无需遵循任何特殊协议即可让这种行为生效;只需要将该方法添加到您的类型中即可。
例如,我们可以创建一个Dice
结构体,它有两个属性:lowerBound 和 upperBound
,然后在类型中添加 callAsFunction
,这样每次调用 Dice
的值时都会得到一个随机值:
struct Dice {
var lowerBound: Int
var upperBound: Int
func callAsFunction() -> Int {
(lowerBound...upperBound).randomElement()!
}
}
let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)
上面的代码将打印一个从1到6的随机数,这与直接使用 callAsFunction()
的效果是相同。例如,我们可以这样调用它:
let d12 = Dice(lowerBound: 1, upperBound: 12)
let roll2 = d12.callAsFunction()
print(roll2)
Swift会根据定义 callAsFunction()
的方式自动调整调用方式。例如,可以根据需要添加任意数量的参数,可以控制返回值,甚至可以根据需要将方法标记为 mutating
。
例如,以下代码将创建一个 StepCounter
结构,该结构跟踪某人已经走了多远,并报告他们是否达到了 10,000
步的目标:
struct StepCounter {
var steps = 0
mutating func callAsFunction(count: Int) -> Bool {
steps += count
print(steps)
return steps > 10_000
}
}
var steps = StepCounter()
let targetReached = steps(count: 10)
还有更高级的用法,callAsFunction() 支持 throws
和 rethrows
,甚至可以在单个类型上定义多个 callAsFunction() 方法,Swift 会根据调用方式选择正确的方法,就像常规重载一样。
下标可以声明默认参数值
将自定义下标添加到类型时,现在可以将默认参数用于任何参数。例如,如果我们有一个带有自定义下标的 PoliceForce
结构体,以从部队中获取军官信息,我们可以添加一个 default
参数,以便在有人尝试读取数组范围之外的索引时返回默认值:
struct PoliceForce {
var officers: [String]
subscript(index: Int, default default: String = "Unknown") -> String {
if index >= 0 && index < officers.count {
return officers[index]
} else {
return `default`
}
}
}
let force = PoliceForce(officers: ["Amy", "Jake", "Rosa", "Terry"])
print(force[0])
print(force[5])
以上代码将打印 "Amy",然后打印 "Unknown",后者是因为数组越界而输出默认值。需要注意的是,如果要让参数可用,则需要重复两次标签,因为下标是不使用参数标签的。
因此,由于我在下标中使用了 default default
,所以可以使用如下自定义值:
print(force[-1, default: "The Vulture"])
lazy 序列的多个 filter 的顺序现在颠倒了
Swift 5.2 中有一个微小变化有可能导致你的功能中断:如果您使用诸如数组之类的惰性序列,并对其应用多个过滤器,则这些过滤器现在将以相反的顺序运行。
例如,下面的代码有一个过滤器,该过滤器选择以S开头的名称,然后另一个过滤器打印出名称,然后返回 true:
let people = ["Arya", "Cersei", "Samwell", "Stannis"]
.lazy
.filter { $0.hasPrefix("S") }
.filter { print($0); return true }
_ = people.count
在Swift 5.2和更高版本中,上述代码将打印“ Samwell”和“ Stannis”,因为在第一个过滤器运行之后,这两个字符串是剩下的进入第二个过滤器的唯一名称。但是在Swift 5.2之前,则会打印所有四个名称,因为第二个过滤器将在第一个过滤器之前运行。这会令人困惑,因为如果删除 lazy
,那么无论Swift 是哪个版本,代码始终只会返回Samwell和Stannis。
这很有问题,因为其行为取决于代码的运行位置:如果您在iOS 13.3或更早版本或macOS 10.15.3或更早版本上运行Swift 5.2代码,则以原始的方式执行,但是在较新的操作系统上运行的相同代码将提供新的正确行为。
因此,此更改可能会导致代码意外中断,但希望这只是短期内的麻烦。
新的改进的诊断体系
Swift 5.2 引入了一种新的诊断体系结构,该体系结构旨在在发生编码错误时提高 Xcode 发出的错误消息的质量和准确性。这在使用SwiftUI代码时尤其明显,因为Swift经常会误报错误消息。
例如,考虑如下代码:
struct ContentView: View {
@State private var name = 0
var body: some View {
VStack {
Text("What is your name?")
TextField("Name", text: $name)
.frame(maxWidth: 300)
}
}
}
这里试图将 TextField 视图绑定到整数 @State 属性,这样做是无效的。在 Swift 5.1
中,报出的错误是 frame()
修饰符出错,提示的是 'Int' is not convertible to 'CGFloat?
,但是在 Swift 5.2 及更高版本中,这可以正确地识别是 $name
的绑定错误:Cannot convert value of type Binding<Int>
to expected argument type Binding<String>
。
近期精彩内容推荐:
在看点这里好文分享给更多人↓↓