WWDC 2022 总结
又是一年一度的 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 引入了一种新的标准方式来获取和表示时间,可分为以下三个部分:
- Clock: 表示当下,并且能提供在将来特定时间点唤起的功能
- Instant: 表示某个瞬间
- 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 上的应用的自定义工具栏。
2.2 标题菜单
可在新的导航栏设置标题菜单,可处理复制、移动、重命名、打印等操作。
2.3 查找和替换
在 UITextView
, WKWebView
, PDFView
有查找和替换功能,只需要设置一个简单的标志位。
2.4 增强的编辑菜单交互
UIMenuContoller
废弃了,取而代之的是 UIEditMenuInteraction
。
根据输入法的不同,编辑交互的样式也不同。
2.5 UICalendarView 日历组件
盼星星盼月亮终于来了,要做好一个日历组件不简单的,虽然有开源方案的选择,但是毕竟没有系统自带的方便。
相比于 UIDatePicker
,UICalendarView
使用 UIDateComponents
作为日期对象,而不是Date
对象。UICalendarView
需要明确提供calendar
,且支持多选日期,以及自定义样式。
2.6 UIPageControl 更新
UIPageControl
带来一个小更新,可以根据不同状态自定义指示图片,还可以设置垂直方向。
2.7 剪贴板访问提示授权弹窗
iOS 15 的时候只是提示哪个 App 访问了剪贴板,iOS 16 会有一个权限弹窗。
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 里,现在可以自定义高度。
或百分比高度
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"时,弹出提示如下图所示:
如果这时回车,那么默认参数将被忽略,函数填充变成:
fetchArticles(authorId: "", keyWords: "")
这里还有一个灰色的提示,当你按着Option
健时,所有灰色的默认参数会变亮,Option + Enter
会自动插入所有参数。
还有一个小技巧是,假如有很多默认参数,但是你只想要自定义某几个默认参数,可以在输入时把默认参数的几个关键字符写入,这样可以指定需要的默认参数,支持模糊搜索。
比如我需要endDate
参数,但不需要startDate
参数,输入"fetchare"即可,最后的"e"代表endDate
。
3.3 AppIcon 简化
在 Xcode 14 之前,App 的桌面图标需要借助工具生成各个尺寸的图标。
Xcode 14 对其进行了简化操作,只需要一张 1024 * 1024 分辨率的图片即可。启用该功能需要将右侧 Devices—iOS 部分改成Single Size