Swift新变化(二) —— Swift 5.2新变化(一)

版本记录

版本号 时间
V1.0 2020.05.02 星期六

前言

几乎随着每一版iOS新系统的发布,Swift都会有所改变,加入了更多的特性,下面我们就一起走进看一下相关的变化。感兴趣的可以看下面几篇文章。
1. Swift新变化(一) —— Swift 5.1新变化(一)

开始

首先看下主要内容:

Swift 5.2现在是Xcode 11.4的一部分。在本文中,您将概述将看到的Swift 5.2的变化。内容来自翻译。

下面看下写作环境

Swift 5, iOS 13, Xcode 11

Swift 5.2现在是Xcode 11.4的一部分。本文概述了您将在最新版本中看到的更改。

总的来说,Swift 5.2只是一个小版本,并不一定是件坏事。它确实带来了许多调整和小的改进,将有助于Swift开发人员的工作流程。在这个版本中,您将发现:

  • 更好的诊断和更有用的错误消息传递,特别是针对SwiftUI
  • 简化某些任务的新功能。
  • 主要的bug修复。

在下面的许多小节中,您将看到对Swift Evolution建议(如SE-0253)Swift bug报告(如SR-11298)的引用。您可以使用提供的链接更深入地研究这些问题。

另外,请跟随本文创建的Playgrounds

首先,您将探索最引人注目的特性:对错误消息的改进。


Improved Diagnostics and Error Messages

你总是在第一次尝试时就写出完美的代码吗?如果没有,您会喜欢Swift 5.2对诊断引擎的改进!当代码中出现错误时,编译器会对错误及其位置给出更准确的描述。

公平地说,Swift编译器已经很好地报告了你代码中的大部分错误。下面的代码:

var str = "10"
let total = str + 5

产生了一个清晰的错误信息

错误消息告诉您不能使用' + '操作符来连接字符串String和整数Int。根据您的意图,它还将您指向修复问题的操作。如果目标是在字符串表示的数字上加上5,那么可以像这样修复错误:

var str = "10"
let total = Double(str)! + 5

然而,在其他时候,错误消息并没有那么有用。与类型检查相关的错误尤其如此。Swift 5.2通过对类型检查器的改进解决了这个问题。

1. Easier Troubleshooting

考虑一下SwiftUI的以下代码:

struct SquareView : View {
  @State var angle = 0.0
  
  var body: some View {
    VStack {
      TextField("Angle:", text: $angle)
      Rectangle()
        .rotation(Angle(degrees: angle))
        .frame(width: 100.0, height: 100.0)
    }
  }
}

Xcode 11.4之前,您会看到这样的错误消息:

An ambiguous error in SwiftUI from older Swift versions

不是很有帮助,是吗?在使用SwiftUI时,经常会看到这样的错误消息。这使得学习使用SwiftUI变得更加困难。即使是最简单的拼写错误,也需要繁琐地重新阅读、删除或注释代码,以隔离实际的错误。

再次查看代码块。你能找出真正的问题吗?提示一下,它与将Double转换为CGFloat没有任何关系!

现在起始项目里打开swiftui.playground。你会看到编译器给你一个更有用和可操作的错误信息:

The improved error from Swift 5.2 and above

错误消息将您指向错误所在的正确行,并告诉您问题所在:您正在将一个绑定Double传递给一个需要绑定到String的方法。

现在您知道如何修复这个错误了,将下面这行代码替换为:

TextField("Angle", value: $angle, formatter: NumberFormatter.decimalFormatter)

2. Not Just SwiftUI

虽然在SwiftUI中你会经常注意到更好的错误消息,但你也会看到其他Swift代码的改进。打开swift52.playground,你会发现这段代码注释掉了:

let x: [Int] = [1, 2, 3, 4]
let w: UInt = 4

let filtered = x.filter { ($0 + w)  > 42 }

这段代码试图在不进行强制转换的情况下将一个Int类型添加到UInt中,因此无法编译。在Swift 5.2之前,编译器显示如下错误:

error: binary operator '+' cannot be applied to operands of type 'Int' and 'UInt'

现在取消注释的代码,你会看到一个更精确和有用的错误消息:

error: new-features.playground:34:22: error: cannot convert value of type 'UInt' to expected argument type 'Int'
_ = x.filter { ($0 + y) > 42 }
^
Int( )

Syntactic Sugar Additions

Swift已经在语言中内置了相当多的syntactic sugarSwift 5.2还增加了两个新特性,某些开发人员会发现这两个新特性非常方便:将类型作为函数调用,并将键路径表达式作为函数使用。

注意:Syntactic sugar改变了语言的语法,使其更容易理解或更简洁。

1. Calling Types as Functions

这个新特性向Swift引入了静态可调用的值。但这意味着什么呢?这意味着你可以像调用函数一样调用类或其他结构。

要在代码中实现此功能,需要向类型添加一个名为callAsFunction(_:)的方法。就是这样!没有第二步。

有了这个加法,现在可以将值作为函数调用。举个例子,这个类型表示一个二次方程——一个常见的数学函数,它的形式是ax^2 + bx + c

struct Quadratic {
  var a: Double
  var b: Double
  var c: Double

  func callAsFunction(_ x: Double) -> Double {
    a * pow(x, 2) + b * x + c
  }
}

你定义你的类型与任何其他值:

let f = Quadratic(a: 4, b: 4, c: 3)

注意,添加callAsFunction(_:)并不会阻止您使用默认的init()方法。

在本例中选择数学类型并不是一个巧合。使用类似于数学符号的语法是这个特性的主要动机。

let y = f(5)

与任何方法一样,您的类型中可以有callAsFunction(_:)的多个重写。如果您还想计算一个双精度Doubles数组的值并以新数组的形式返回结果,您可以添加callAsFunction(_:)的第二个实现,如下所示:

func callAsFunction(_ xs: [Double]) -> [Double] {
  xs.map { callAsFunction($0) }
}

这段代码接受一个双精度数组,并使用map()callAsFunction(_:)的前一个实现来生成一个包含结果的数组。Swift决定调用适当的方法,就像它调用任何其他被覆盖的函数一样。

let z = f([5, 7, 9])

2. Cleaner Syntax

您可以通过在这个类型上添加一个传统方法来实现相同的结果。但是新的语法更简洁,特别是当一个值只有一个明显的操作时。在本例中,二次型Quadratic的明显作用是计算给定x的方程值。类似地,解析器Parser通常将解析输入作为其主要函数。

3. Machine Learning Application

这个特性似乎特别侧重于机器学习。您可以在最初的提案中看到这一点,因为它特别提到了清理神经网络应用程序的语法。它与Python相似,并且是跨兼容的。所有这些都使Swift更适合ML开发人员。

注意:有关此建议的更多信息,请参见GitHub页面:SE-0253: Callable values of user-defined nominal types

4. Key Path Expressions as Functions

Swift 5.2中,该语言现在允许使用\Root. value值键路径表达式,只要你已经可以使用(Root) -> Value函数。

在代码方面,你可以写:

orders.map { $0.email }

你也可以写成:

orders.map(\.email)

当前实现将此功能限制为键路径文字表达式。你现在还不能写下面的内容,但是对这个特性的讨论建议了将来的实现:

let kp = \.email
users.map(kp)

然而,你可以用不同的语法来完成类似的事情:

let asEmail: (Order) -> String = \Order.email
orders.map(asEmail)

前两个功能为Swift带来了更好的功能。现在可以在以前需要块或闭包的地方使用函数。既然您现在可以将键路径作为函数传递,那么您也可以将键路径传递给实现新callAsFunction()的值。

注意:有关此建议的更多信息,请参见GitHub页面: SE-0249: Key Path Expressions as Functions

5. Subscripts With Default Arguments

使用Swift 5.2,您现在可以在为类型添加自定义下标时声明默认参数。例如,这里有一个简单的类型,它使用下标来进行乘法运算:

struct Multiplier {
  subscript(x: Int, y: Int = 1) -> Int {
    x * y
  }
}

let multiplier = Multiplier()

这个添加允许您编写指定任意数量的可用下标的代码。Swift对未指定的下标使用默认值:

multiplier[2, 3]
multiplier[4]

注意:有关更改的更多信息,请参见本文的Swift.org: SR-6118: Mechanism to hand through #file/#line in subscripts


Major Bug Fixes

虽然新特性在新版本中最受关注,但是修复bug也很重要。接下来您将了解这些。

1. Lazy Filters are Called in Order

当对一个延迟序列或集合调用filter(_:)进行链接时,过滤谓词现在的调用顺序与eager filter相同。这个bug修复是最有可能破坏现有代码的一个。对于大多数集合,Swift按顺序调用过滤谓词。所以这段代码:

let array = ["1", "2", "3"]
let filtered = array
  .filter { _ in
    print("A")
    return true
  }
  .filter { _ in
    print("B")
    return true
  }

_ = Array(filtered)

将会输出:

A
A
A
B
B
B

Swift 5.2之前,对一个延迟序列或集合按相反的顺序求值。把这个例子:

let lazyFiltered = array.lazy
  .filter { _ in
    print("A")
    return true
  }
  .filter { _ in
    print("B")
    return true
  }

_ = Array(lazyFiltered)

你希望按照下面进行输出

A
B
A
B
A
B

但是实际上输出为

B
A
B
A
B
A

当用Swift 5.2编译时,结果按预期顺序打印。如果已经编写了依赖于反向执行的代码,则需要更新代码以反映修复的行为。

注意:有关此bug修复的更多信息,请参阅本文:Swift.org: SR-11841: Lazy filter runs in unexpected order

Swift 5.2中剩余的bug修复对现有代码的影响较小,但值得注意。如果您在过去处理过这些问题,您就会想知道这些变化。

2. Default Values From Outer Scopes

编译器现在支持本地函数,其默认参数从外部作用域捕获值。这允许这样的代码:

func outer(x: Int) -> (Int, Int) {
  func inner(y: Int = x) -> Int {
    return y
  }

  return (inner(), inner(y: 0))
}

Swift 5.2之前,上述代码不能工作。

注意:有关此bug修复的更多信息,请参阅本文:Swift.org: SR-2189: Nested function with local default value crashes

3. Warning When Passing Dangling Pointers

编译器现在,当你试图传递一个临时指针参数,试图超过调用时会显示一个警告。这将包括以下代码:

func generatePointer() {
  var number: Int8 = 0
  let pointer = UnsafePointer(&number)
}

这将导致一个警告:

warning: initialization of 'UnsafePointer' results in a dangling pointer

注意:留下悬空指针的代码几乎总是表示错误。该语言的未来版本可能会指出这一点,而不是仅仅给出警告。
有关此错误修复的更多信息,请参阅本文:Swift.org: SR-2790: Reject UnsafePointer initialization via implicit pointer conversion

4. Overridden Methods Can’t Use Incorrect Generics

在此之前,您可以使用与基方法的泛型签名不兼容的泛型签名来编写覆盖方法。例如,你可以这样写:

protocol P { }

class Base {
  func doWork(input: T) { }
}

class Derived: Base {
  override func doWork (input: T) { }
}

在Swift 5.2中,这段代码不再编译,现在会抛出一个错误。

注意:有关此bug修复的更多信息,请参阅本文:SR-4206: Override checking does not properly enforce requirements

5. Class-Constrained Protocol Extensions

一个受类约束的协议扩展(扩展的协议不施加类约束)现在将隐式地推断约束。考虑以下代码:

protocol Foo {}

class Bar: Foo {
  var someProperty: Int = 0
}

extension Foo where Self: Bar {
  var anotherProperty: Int {
    get { return someProperty }
    set { someProperty = newValue }
  }
}

在这里,Foo没有强加一个类约束。但编译器
推断这是由于Foo上的Self: Bar约束。这导致setter变得隐式非可变,就像Foo有一个类约束一样。

注意:有关此bug修复的更多信息,请参见本文的Swift.org: SR-11298: Writable property declaration in a conditional-conforming protocol extension has incorrect mutability

6. Disambiguate Functions with Named Parameters

现在可以使用as操作符消除对带有参数标签的函数的调用的歧义。以前,只能对没有标签参数的函数执行此操作。考虑这两个函数:

func print(x: Int) { print("Int \(x)") }
func print(x: UInt) { print("UInt \(x)") }

现在你可以用带有as的下面的语法来区分这两个函数:

(print as (Int) -> Void)(5) // Prints Int 5
(print as (UInt) -> Void)(5) // Prints UInt 5

这种更改有一个副作用:您不能再使用泛型typealias来保存使用as操作符的函数引用的参数标签。这方面的一个例子是:

typealias Magic = T
(print as Magic)(x: 5)

这段代码现在将导致编译错误:

Extraneous argument label 'x:' in call

相反,你必须消除调用中的参数:

(print as Magic)(5)

这输出Int 5,就像上面的第一个调用一样。

注意:有关此错误修复的更多信息,请参阅本文: SR-11429: Don’t look through CoerceExprs in markDirectCallee

虽然Swift 5.2并不是一个重大的更新,但它确实给语言带来了可喜的变化和补充。几乎所有开发人员都将受益于诊断和错误消息方面的改进。如果他们在他们的项目中使用机器学习,他们也会欣赏新的类型和关键路径特性。使用自定义集合的开发人员将欢迎添加默认的下标类型。

如果你想了解更多关于Swift Evolution的过程,看看这些链接:

  • GitHub change log for Swift。本文档简要总结了每个版本的更改。
  • GitHub listing of Swift Evolution proposals。本文档为每个提案提供到GitHub页面的链接。用这个来阅读一个给定变化背后的原因和思考过程的完整描述。
  • Swift.org bugs and issues dashboard。使用本网站查看在Swift中报告的bugs and issues完整的描述,讨论和活动。

后记

本篇主要讲述了Swift 5.2新变化,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(Swift新变化(二) —— Swift 5.2新变化(一))