美文网首页iOS SwiftSwift
Swift新变化(二) —— Swift 5.2新变化(一)

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

作者: 刀客传奇 | 来源:发表于2020-05-02 22:25 被阅读0次

    版本记录

    版本号 时间
    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新变化,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:Swift新变化(二) —— Swift 5.2新变化(一)

        本文链接:https://www.haomeiwen.com/subject/ldsdfhtx.html