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
之前,您会看到这样的错误消息:
不是很有帮助,是吗?在使用SwiftUI
时,经常会看到这样的错误消息。这使得学习使用SwiftUI
变得更加困难。即使是最简单的拼写错误,也需要繁琐地重新阅读、删除或注释代码,以隔离实际的错误。
再次查看代码块。你能找出真正的问题吗?提示一下,它与将Double
转换为CGFloat
没有任何关系!
现在起始项目里打开swiftui.playground
。你会看到编译器给你一个更有用和可操作的错误信息:
错误消息将您指向错误所在的正确行,并告诉您问题所在:您正在将一个绑定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 sugar
。Swift 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<Int8>' results in a dangling pointer<int8>
注意:留下悬空指针的代码几乎总是表示错误。该语言的未来版本可能会指出这一点,而不是仅仅给出警告。
有关此错误修复的更多信息,请参阅本文: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<T>(input: T) { }
}
class Derived: Base {
override func doWork <T: P>(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> = 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新变化,感兴趣的给个赞或者关注~~~