Swift 5.2 的几个新特性

作者 | 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>

近期精彩内容推荐:  

 太真实了!有个程序员男友原来是这样的!

 终于有人把 HTTPS 原理讲清楚了!

 你真的会用 Postman 吗?

 Swift 5.3 路线图

在看点这里好文分享给更多人↓↓

Swift 是编写程序的绝佳选择,无论是手机、电脑还是服务器,任何能跑代码的设备都是如 此。它是一门集现代语言之大成,集结了苹果的工程师文化精髓以及开源社区多样化于一身的 编程语言。编译器为专为性能所调优,语言专为开发所优化,二者绝不互相妥协。 Swift 语言还对新的程序员十分友好。它是第一个工业级系统编程语言,却又像脚本语言那样 富有张力且十分有趣。在 Playground 编写代码并实时查看 Swift 代码运算结果,完全不需要 从头编译然后运行软件。 Swift 通过向其他现代编程模式学习,定义了大量类来避免常⻅的编程错误: 变量一定是在使用前初始化的; 数组索引会检查越界错误; 整数会检查溢出; 可选项保证了 nil 值会显式处理; 内存自动管理; 错误处理允许从意外错误中恢复控制。 Swift 代码为大部分现代硬件编译和优化。语法和基本库都基于指导原则设计,这显然也是你 让代码的最佳方式。这使得集安全和速度于一身的 Swift 适用于任何场景,从编写 “Hello, world!”到整个操作系统,都是上上之选。 Swift 用轻量级的语法集合强大的类型接口和模式匹配,能够把复杂的想法以简洁优雅的形式 表达。得益于此,代码不仅仅更好写了,还变得更加易读和益于优化。 Swift 已经投放市场多年,它还会继续演进,带来更多的新特性和新功能。我们对 Swift 语言充 满了信心,我们期待着你用 Swift 进行创作!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值