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<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的过程,看看这些链接:

后记

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342