Skip to main content

WWDC 2022 总结

· 14 min read

又是一年一度的 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。但是 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 种类型:

  • Navigator: iOS 16 之前的样式
  • Browser: 浏览器样式,可以用于支持"History"之类的 web 浏览器功能,或者侧边目录结构的文档浏览器。
  • Editor: 编辑器样式支持在中间点击的时候会弹窗,然后拖拽功能按钮到导航栏,类似于 macOS 上的应用的自定义工具栏。

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之间传递,并发环境下安全使用:

  • UIImage
  • UIColor
  • UIFont
  • UITraitCollection

2.10 Stage Manager

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

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

  • UIScreen.main
  • UIScreen 声明周期的通知

取而代之的是 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 编译速度改进

  • 支持了并行编译,速度提升 25%
  • Test 也支持并行编译,速度提升 30%
  • Interface Builder 性能改进,比如 Storyboard,加载文档的速度提升 50%,切换不同设备的速度 30%
  • 支持Build Timeline,可以查看每个类的编译时间。

UhMMoz

4. 参考

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