hiccLoghicc log by wccHipo日志

Swift 5.1

toc

Intro

又是一篇迟来的文章。主要是译自: 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中static或者class方法的区别

告警有歧义的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越来越好用。😄