又是一篇迟来的文章。主要是译自: What’s new in Swift 5.1

阅读之前可以查看我之前总结的Swift 3 到Swift 5的新特性

Swift5.1支持了SwiftUI, 可以看出本次的更新更多了是为了更好的“声明”UI。


改进自动生成的struct初始化方法

swift早期版本中支持了自动生成struct的初始化方法

struct User {
    var name: String
    var loginCount: Int = 0
}

let piper = User(name: "Piper Chapman", loginCount: 0)

Swift 5.1改进了初始化方法,如果属性有了初始值,初始化的时候可以省略。

let gloria = User(name: "Gloria Mendoza", loginCount: 0)
let suzanne = User(name: "Suzanne Warren")

单行函数隐式返回

这点实际上很好理解,Swift 5.1中单行返回函数可以省略return关键词。

let doubled1 = [1, 2, 3].map { $0 * 2 }
let doubled2 = [1, 2, 3].map { return $0 * 2 }

上面两个是等价的。

实际上下面这么写也一样

func double(_ number: Int) -> Int {
    number * 2
}

更通用的Self

Swift 5.1中扩展了Self的使用,在class,struct, enmu中使用时候可以指向这个类型。这点在动态类型中很有用,特别是某些类型需要在运行时才能决定时候。

例如,下面场景中

class NetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }

    func printDebugData() {
        print("Maximum network requests: \(NetworkManager.maximumActiveRequests).")
    }
}

上面声明了静态的maximumActiveRequests属性,并且使用实例方法printDebugData来打印这个属性。现在这样工作的挺好。但是如果NetworkManager有子类,情况就不一样了

class ThrottledNetworkManager: NetworkManager {
    override class var maximumActiveRequests: Int {
        return 1
    }
}

子类改变了maximumActiveRequests,但是如果我们调用printDebugData(),它只会打印父类的属性。

let manager = ThrottledNetworkManager()
manager.printDebugData()

它理应打印1而不是4,这就是Self要解决的问题。我们改写printDebugData()Self(大写S)来指向当前的类型:

class ImprovedNetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }

    func printDebugData() {
        print("Maximum network requests: \(Self.maximumActiveRequests).")
    }
}

Self在协议中仍然像早期Swift中一样工作。

不透明返回类型(Opaque Return types)

提案SE-0244为Swift 5.1带来了不透明类型:知晓某个对象的能力但是不需要知道这个对象的具体类型。

初看之下,它很像协议protocol,但不透明返回类型走的比这更远,它可以和associated type使用。

protocol Fighter { }
struct XWing: Fighter { }

func launchFighter() -> Fighter {
    return XWing()
}

let red5 = launchFighter()

上面launchFighter返回某种Fighter,但是不知道具体那个类型,它可以是XWing,或者我们新增一个strunct YWing:Fighter {}

这样写有个问题,如果你想知道red5具体是那种类型的飞机呢?你可能想到方案是,让Fighter遵循Equatable协议,然后我们就可以使用==方法。但是实际使用的时候会发现下面的报错:

“Protocol 'Fighter' can only be used as a generic constraint because it has Self or associated type requirements.”

这是因为Equatable有一个Self的associated type。 有associated type的协议看起来像类型,但是它们实际上不是,它们实际上表示的是“遵循此协议的任意类型”

Swift 5.1中的不透明返回类型,可以将这种协议作做一个普通的类型来使用。只需要在协议名前增加some关键词。

func launchOpaqueFighter() -> some Fighter {
    return XWing()
}

不透明返回类型(Opaque Return types)可以带来的好处有:

  • 我们的函数决定具体的返回类型,而不是函数的调用方。
  • 我们不需要在关心Self或者associated type,因为编译器会明确知道内部具体的类型。
  • 为函数定义方将来改变实现留有余地。
  • 函数定义方不需要对外保留内部的类型。

支持Staticclass类下标(subscripts)

静态Static类型的属性和方法,可用来在类型所有实例间共享某些值。例如你可以在你的App中集中管理配置。

public enum OldSettings {
    private static var values = [String: String]()

    static func get(_ name: String) -> String? {
        return values[name]
    }

    static func set(_ name: String, to newValue: String?) {
        print("Adjusting \(name) to \(newValue ?? "nil")")
        values[name] = newValue
    }
}

OldSettings.set("Captain", to: "Gary")
OldSettings.set("Friend", to: "Mooncake")
print(OldSettings.get("Captain") ?? "Unknown")

字典包裹在类型中可以让更小心的控制它,并且使用没有caseenum,也就让你没法实例化Settings

在Swift 5.1中你可以使用static subscript

public enum NewSettings {
    private static var values = [String: String]()

    public static subscript(_ name: String) -> String? {
        get {
            return values[name]
        }
        set {
            print("Adjusting \(name) to \(newValue ?? "nil")")
            values[name] = newValue
        }
    }
}

NewSettings["Captain"] = "Gary"
NewSettings["Friend"] = "Mooncake"
print(NewSettings["Captain"] ?? "Unknown")
  • 类型实例之前就可以自定义下标
  • swift 5.1中类型也可以定制static或者class的下标了。

static或者class都是静态的前缀,区别是,class容许子类型覆盖

swift中或者方法的区别

告警有歧义的none

Swift的可选(optional)是现实是通过有两个值somenone的enum来实现的。这样就和我们自己代码中有nonecase的enum包裹在optional时候产生混淆。

enum BorderStyle {
    case none
    case solid(thickness: Int)
}

如果在非可选值中使用

let border1: BorderStyle = .none
print(border1)

上面会打印none。 但是如果我们在使用在可选值中,我们不知道什么边框时候,Swift 5.1之前的版本会有问题。

let border2: BorderStyle? = .none
print(border2)

会打印nil, 因为swfit会默认将.none推导为可选是空值,而不是BorderStyle.none

Swift 5.1中会对此做出警告:“Assuming you mean 'Optional.none'; did you mean 'BorderStyle.none' instead?”,提示你作修复。

匹配可选(optional)和非可选的(non-optional)的enmu

Swift一直能够在switch case聪明的处理可选(optional)和非可选的(non-optional)的string,和integer。到了Swift 5.1 也支持enum了。

enum BuildStatus {
    case starting
    case inProgress
    case complete
}

let status: BuildStatus? = .inProgress

switch status {
case .inProgress:
    print("Build is starting…")
case .complete:
    print("Build is complete!")
default:
    print("Some other build status")
}

现在能够正常的打印,”Build is starting…“了。

可排序集合的diff

Swift 5.1 为可排序集合(内含Equatable元素)提供了一个difference(from:)方法来计算两个集合,那个元素被移除了,新增了哪个……

此方法被标注为,swift 5.1才@available,因此使用需要if swift(>=5.1)来判断

let operatingSystems = ["Yosemite",
                        "El Capitan",
                        "Sierra",
                        "High Sierra",
                        "Mojave",
                        "Catalina"]
var answers = ["Mojave",
               "High Sierra",
               "Sierra",
               "El Capitan",
               "Yosemite",
               "Mavericks"]


#if swift(>=5.1)
let differences = operatingSystems.difference(from: answers)
let sameAnswers = answers.applying(differences) ?? []
// 1
for change in differences.inferringMoves() {
  switch change {
    // 2
    case .insert(let offset, let element, let associatedWith):
      answers.insert(element, at: offset)
      guard let associatedWith = associatedWith else {
        print("\(element) inserted at position \(offset + 1).")
        break
      }
      print("""
            \(element) moved from position \(associatedWith + 1) to position
            \(offset + 1).
            """)
    // 3
    case .remove(let offset, let element, let associatedWith):
      answers.remove(at: offset)
      guard let associatedWith = associatedWith else {
        print("\(element) removed from position \(offset + 1).")
        break
      }
      print("""
            \(element) removed from position \(offset + 1) because it should be
              at position \(associatedWith + 1).
            """)
  }
}
#endif
  • 使用inferringMoves()得到diff中所欲改变
  • 通过associatedWith不为nil.insert(offset:element:associatedWith:)来判断是插入的操作
  • 通过associatedWith不为nil.remove(offset:element:associatedWith:)来判断是删除的操作

这个功能确实非常的pro哈~~

创建没有初始值的数组(uninitialized arrays)

可以在Swfift 5.1的版本中创建一个没有初始值的数组。

// 1
let randomSwitches = Array<String>(unsafeUninitializedCapacity: 5) {
  buffer, count in
  // 2
  for i in 0..<5 {
    buffer[i] = Bool.random() ? "on" : "off"
  }
  // 3
  count = 5
}
  1. 使用init(unsafeUninitializedCapacity:initializingWith:)来创建一个初始大小的数组
  2. 循环该数组(randomSwitches), 给每个值设值。
  3. 第二个inout参数可以让你重新为数组设置长度。

总结

这些就是swift 5.1的更新了,不算难懂,确实让swift越来越好用。😄