Skip to content
Calvin's Blog

WWDC 2022 总结

Jul 17, 2022 — iOS

又是一年一度的 WWDC,例行总结一下。

1. Swift 5.7 更新内容

1.1 if let简化

当一个变量名非常长时,if let只能像这样:

var thisIsALongNameOfParameter: String? = nil
if let thisIsALongNameOfParameter = thisIsALongNameOfParameter {
print(thisIsALongNameOfParameter)
}

上面的看起来很啰嗦,有时为了简化,会取一个短的名字

var thisIsALongNameOfParameter: String? = nil
if let name = thisIsALongNameOfParameter {
print(name)
}

Swift 5.7 之后,可以缩短成if let + 变量名即可,guard let 也是类似的。

var thisIsALongNameOfParameter: String? = nil
if let thisIsALongNameOfParameter {
print(thisIsALongNameOfParameter)
}

但这个简写功能只能用于对象本身,不能用于对象的属性。

下面这种就会报错:

struct Book {
let price: Float?
}
let book: Book = Book(price: 32.0)
if let book.price {
print(book.price ?? 0.0)
}

1.2 闭包的类型推断

Swift 5.7 之前,闭包的类型推断只能用于单个表达式,比如下面的results可以通过编译器的类型推断出是[String]类型:

var numbers = [4, 3, 2, 1]
let results = numbers.map {
"\($0)"
}

但当里面有多个表达式的时候,比如if else判断或do catch等条件表达式时,编译器就会报错误

let results = numbers.map {
if $0.isMultiple(of: 2) {
return "\($0)"
} else {
return "\($0 * 2)"
}
}
// error: cannot infer return type for closure with multiple statements; add explicit type to disambiguate
// let results = numbers.map {

解决办法也很简单,指定闭包的返回值即可:

let results = numbers.map { n -> String in
if n.isMultiple(of: 2) {
return "\(n)"
} else {
return "\(n * 2)"
}
}

Swift 5.7 对上面这种情况做了简化处理,编译器可以推断出多个表达式的类型,应该是推断return的类型,无需手动指定返回类型了。

但这个功能的前提条件是所有的return类型都是同一类型,不能return不同的类型,如下面的情况会报编译器错误。

let results = numbers.map { n in
if n.isMultiple(of: 2) {
return "\(n)"
} else {
return n * 2
}
}

1.3 从内存安全到线程安全

内存安全

在 Swift 5.7 之前,我们在做数组删除操作时,又去访问数组的长度,编译器会报错,这个叫内存安全。

var numbers = [4, 3, 2, 1]
numbers.removeAll(where: { number in
number == numbers.count
})

编译器报错

Overlapping accesses to ‘numbers’, but modification requires exclusive access; consider copying to a local variable

线程安全

Swift 5.7 中,当我们在Task里面操作数组时一样,编译器同样会报警告:

var numbers = [3, 2, 1]
Task {
numbers.append(0)
}
numbers.removeLast()
print(numbers)

编译器警告

Mutation of captured var ‘numbers’ in concurrently-executing code; this is an error in Swift 6

1.4 Swift Regex

我一直认为正则表达式的可读性非常差,所以 Apple 在 Objective-C 时代推出了NSPredicate (opens in a new window)。但是 Swift 时代,NSPredicate 这种偏重字符串格式化的方法不符合强类型的思想,所以 Regex 诞生了。我认为这是可以预见的结果,通过函数式的方式组合各种条件达到非常复杂的正则效果。

看下面的例子:

import RegexBuilder
let regex = Regex {
ZeroOrMore(.horizontalWhitespace)
Optionally {
Capture(OneOrMore(.noneOf("<#")))
}
.repetitionBehavior(.reluctant)
ZeroOrMore(.horizontalWhitespace)
"<"
Capture(OneOrMore(.noneOf(">#")))
">"
ZeroOrMore(.horizontalWhitespace)
ChoiceOf {
"#"
Anchor.endOfSubjectBeforeNewline
}
}

非常的语义化,一眼就看的出来是什么意思。

唯一的缺点就是只能在 iOS 16 上使用,遗憾。

1.5 时间标准

Swift 5.7 引入了一种新的标准方式来获取和表示时间,可分为以下三个部分:

  1. Clock: 表示当下,并且能提供在将来特定时间点唤起的功能
  2. Instant: 表示某个瞬间
  3. Duration: 用于计量流逝的时间

Clock 有 ContinuousClock 和 SuspendingClock 两种内置时钟,ContinuousClock 在系统休眠时也会保持时间递增,而 SuspendingClock 则不会。Task 休眠相关的 API 也会根据新标准有所更新。

extension Task {
@available(*, deprecated, renamed: "Task.sleep(for:)")
public static func sleep(_ duration: UInt64) async
@available(*, deprecated, renamed: "Task.sleep(for:)")
public static func sleep(nanoseconds duration: UInt64) async throws
public static func sleep(for: Duration) async throws
public static func sleep<C: Clock>(until deadline: C.Instant, tolerance: C.Instant.Duration? = nil, clock: C) async throws
}

2. UIKit 16 更新内容

2.1 改进的导航栏

Navigation Bar 现在有 3 种类型:

dbGU91

2.2 标题菜单

可在新的导航栏设置标题菜单,可处理复制、移动、重命名、打印等操作。

taTuhM

2.3 查找和替换

UITextView, WKWebView, PDFView有查找和替换功能,只需要设置一个简单的标志位。

yFbeFF

2.4 增强的编辑菜单交互

UIMenuContoller废弃了,取而代之的是 UIEditMenuInteraction。 根据输入法的不同,编辑交互的样式也不同。

1hm7RF

2.5 UICalendarView 日历组件

盼星星盼月亮终于来了,要做好一个日历组件不简单的,虽然有开源方案的选择,但是毕竟没有系统自带的方便。

相比于 UIDatePickerUICalendarView 使用 UIDateComponents作为日期对象,而不是Date对象。UICalendarView需要明确提供calendar,且支持多选日期,以及自定义样式。

7aBgML

BnUcVz

2.6 UIPageControl 更新

UIPageControl带来一个小更新,可以根据不同状态自定义指示图片,还可以设置垂直方向。 LGFWCB

2.7 剪贴板访问提示授权弹窗

iOS 15 的时候只是提示哪个 App 访问了剪贴板,iOS 16 会有一个权限弹窗。

0u9IVl

2.8 UISheetpresentationController 更新

UISheetpresentationController 在 iOS 15 的时候发布,但是那时的 detent 只能设置两个预设的值.medium().large(),高度要么一半,要么全屏,灵活性不高。

// In a subclass of UIViewController, customize and present the sheet.
func showMyViewControllerInACustomizedSheet() {
let viewControllerToPresent = MyViewController()
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(viewControllerToPresent, animated: true, completion: nil)
}

在 iOS 16 里,现在可以自定义高度。

E0a6SN

或百分比高度

sheet.detents = [
.large(),
.custom { context in
0.3 * context.maximumDetentValue
}
]

以及自定义 detent:

extension UISheetPresentationController.Detent.Identifier {
static let small = UISheetPresentationController.Detent.Identifier("small")
}
sheet.detents = [
.large(),
.custom (identifier: .small) { context in
0.3 * context.maximumDetentValue
}
]
sheet.largestUndimmedDetentIdentifier = .small

2.9 Swift Concurrency and Sendable

在 iOS 16 中以下几个类型遵循sendable协议,可以自由的在各个 actor之间传递,并发环境下安全使用:

2.10 Stage Manager

为了鼓励开发者适配多窗口支持,Stage Manager 应运而生。

与此同时,以下两个东西被废弃了:

取而代之的是 trait collection 和 UIScene 的 API。

2.11 自适应大小的 Cell

iOS 16 中, UICollectionView 和 UITableView 默认开启了这一功能。

class UICollectionView {
// 默认是 .enabled
var selfSizingInvalidation: SelfSizingInvalidation
}

当使用 UIListContentConfiguration(iOS 14)时,自适应大小功能会无效。

还可以使用invalidateIntrinsicContentSize() 手动使其无效。

当然我们还可以依靠 auto layout 来告诉 cell 什么时候不需要自适应大小:

selfSizingInvalidation = .enabledIncludingConstraints

contentView的 Auto Layout 改变时,自适应大小会无效。

2.12 UIHostingConfiguration

为了改进 UIKit 和 SwiftUI 的互操作性,引入了UIHostingConfiguration,可以很方便的在 UIKit 的 cell 中使用 SwiftUI:

cell.contentConfiguration = UIHostingConfiguration {
// SwiftUI 代码
}

2.13 UIDevice 废弃接口

为了隐私问题,UIDevice.name不会返回当前设备的名称,类似’xxx 的 iPhone X’这样的字符串,这个是用户可以在设置中修改的。 在 iOS 16 中,UIDevice.name只会返回设备的型号。

设备的横竖屏方向UIDevice.orientation也废弃了,用PreferredInterfaceOrientation替代。

3. Xcode 14 更新

Xcode 本次更新可说是干货满满,对提高开发效率还是非常巨大的。

3.1 可选安装

安装包减少了 30%,下载之后首次打开会提醒让你选择模拟器,iOS 和 macOS 内置了,也无法取消,watchOS 和 tvOS 是可选的,这两个每个大概 3G 左右的大小。

3.2 代码完成

先定义一个 User 类型的 struct:

struct User: Identifiable {
var id = UUID()
let name: String
let age: Int
let gender: Int
}

3.2.1 自动补全初始化方法

当你在 Editor 输入init时,会自动帮你生成如下初始化代码

init(id: UUID = UUID(), name: String, age: Int, gender: Int) {
self.id = id
self.name = name
self.age = age
self.gender = gender
}

3.2.2 自动补全Codable方法

我们给User增加Codable协议,可以自动补全三块内容。而这些东西有时候需要借助第三方工具(比如 QuickType)才能更方便生成,现在 Xcode 直接内置了,就很爽啊。

输入”coding”,补全CodingKeys:

enum CodingKeys: CodingKey {
case id
case name
case age
case gender
}

输入”init”,补全init(from decoder: Decoder)

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.gender = try container.decode(Int.self, forKey: .gender)
}

输入”encode”,补全func encode(to encoder: Encoder)

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.name, forKey: .name)
try container.encode(self.age, forKey: .age)
try container.encode(self.gender, forKey: .gender)
}

3.2.3 函数默认参数自动隐藏

假设我们定义了一个fetchArticles的函数,它有 2 个必填参数和 2 个默认参数。

当我们输入”fetchar”时,弹出提示如下图所示:

IhE0SD

如果这时回车,那么默认参数将被忽略,函数填充变成:

fetchArticles(authorId: "", keyWords: "")

这里还有一个灰色的提示,当你按着Option健时,所有灰色的默认参数会变亮,Option + Enter会自动插入所有参数。

还有一个小技巧是,假如有很多默认参数,但是你只想要自定义某几个默认参数,可以在输入时把默认参数的几个关键字符写入,这样可以指定需要的默认参数,支持模糊搜索。

比如我需要endDate参数,但不需要startDate参数,输入”fetchare”即可,最后的”e”代表endDate

WmSiX2

3.3 AppIcon 简化

在 Xcode 14 之前,App 的桌面图标需要借助工具生成各个尺寸的图标。

Xcode 14 对其进行了简化操作,只需要一张 1024 * 1024 分辨率的图片即可。启用该功能需要将右侧 Devices—iOS 部分改成Single Size

LqdIri

3.4 编译速度改进

UhMMoz

4. 参考

  1. What’s new in Swift (opens in a new window)
  2. What’s new in SwiftUI (opens in a new window)
  3. What’s new in Xcode (opens in a new window)
  4. What’s new in UIKit (opens in a new window)
  5. Customize and resize sheets in UIKit (opens in a new window)
  6. Eleminate data races using swift concurrency (opens in a new window)
  7. Meet Swift Regex (opens in a new window)
  8. Swift Regex: Beyond the basics (opens in a new window)