Swift5.5、DocC、Notifications,WWDC21 带来的最大技术变化
WWDC (苹果开发者大会) 2021已经落下帷幕,今年的WWDC 提供了 200 多个深度课程,以帮助开发者了解WWDC2021 引入的新技术,本文会帮国内开发者梳理部分WWDC 2021带来的技术上的变化。
Swift5.5
WWDC2021 给我们带来了Swift 5.5,这是Swift 语言最新的版本,在这个版本中有许多重大的更新,下面会大家详细介绍一下Swift 5.5的一些重要更新。
▐ Swift Concurrency
Swift 5.5 中最大的更新就是引入了全新的并发编程方式,包括async/await语法、结构化并发、Actor等,新的并发编程方式解决了我们以往使用回调的种种缺陷 (嵌套地狱、回调错误处理麻烦、回调分支编写困难等),为开发者带来了极大的便利。
async/await
过去我们编写异步代码都是通过回调的方式,如下:
func processImageData2a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) {loadWebResource("dataprofile.txt") { dataResource, error inguard let dataResource = dataResource else {completionBlock(nil, error)return}loadWebResource("imagedata.dat") { imageResource, error inguard let imageResource = imageResource else {completionBlock(nil, error)return}decodeImage(dataResource, imageResource) { imageTmp, error inguard let imageTmp = imageTmp else {completionBlock(nil, error)return}dewarpAndCleanupImage(imageTmp) { imageResult, error inguard let imageResult = imageResult else {completionBlock(nil, error)return}completionBlock(imageResult)}}}}}processImageData2a { image, error inguard let image = image else {display("No image today", error)return}display(image)}
通过回调的方式编写异步代码有以下缺点:
阅读不直观
嵌套逻辑复杂
错误处理麻烦
分支逻辑难以处理
经常会忘了回调或者返回
在Swift 5.5中为了解决上述回调方式的缺点,引入了async/await语法,可以帮助我们快速的编写异步代码,通过async/await上述代码可以变成如下同步代码:
func loadWebResource(_ path: String) async throws -> Resourcefunc decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Imagefunc dewarpAndCleanupImage(_ i : Image) async throws -> Imagefunc processImageData() async throws -> Image {let dataResource = try await loadWebResource("dataprofile.txt")let imageResource = try await loadWebResource("imagedata.dat")let imageTmp = try await decodeImage(dataResource, imageResource)let imageResult = try await dewarpAndCleanupImage(imageTmp)return imageResult}
正如上述代码所展现的,所有的闭包和缩进都消失了,你可以按顺序依次书写代码——除了 await 关键字,它看起来就和同步代码一样。
关于async函数的工作方式,有一些规则需要注意:
同步函数不能简单地直接调用异步函数, Swift 编译器会抛出错误。
异步函数可以调用其他异步函数,但如果需要,它们也可以调用常规的同步函数。
如果有可以以相同方式调用的异步和同步函数,Swift 将优先选择与当前上下文匹配的任何一个, 如果当前执行上下文是异步的,那么 Swift 将调用异步函数,否则它将调用同步函数。
最后一点很重要,因为它允许库的作者提供他们代码的同步和异步版本,而无需专门命名异步函数。
Structured concurrency
在介绍结构化并发之前,我们先来看一个案例:
func chopVegetables() async throws -> [Vegetable] { ... }func marinateMeat() async -> Meat { ... }func preheatOven(temperature: Double) async throws -> Oven { ... }// ...func makeDinner() async throws -> Meal {let veggies = try await chopVegetables() // 处理蔬菜let meat = await marinateMeat() // 腌制肉let oven = try await preheatOven(temperature: 350) //预热烤箱let dish = Dish(ingredients: [veggies, meat]) // 把蔬菜和肉装盘return try await oven.cook(dish, duration: .hours(3)) // 用烤箱做出晚餐}
上面处理蔬菜、腌制肉、预热烤箱等都是异步执行的,但是上述三个步骤仍然是串行执行的,这使得做晚餐的时间变长了,为了让晚餐准备时间变短,我们需要让处理蔬菜、腌制肉、预热烤箱几个步骤并发执行
为了解决上述问题,Swift 5.5中引入了Structured concurrency(结构化并发),下面是维基百科中的解释:
结构化并发是一种编程范式,旨在通过使用结构化的并发编程方法来提高计算机程序的清晰度、质量和研发效能。
核心理念是通过具有明确入口和出口点并确保所有生成的子任务在退出前完成的控制流构造来封装并发执行任务(这里包括内核和用户线程和进程)。这种封装允许并发任务中的错误传播到控制结构的父作用域,并由每种特定计算机语言的本机错误处理机制进行管理。尽管存在并发性,但它允许控制流通过源代码的结构保持显而易见。为了有效,这个模型必须在程序的所有级别一致地应用——否则并发任务可能会泄漏、成为孤立的或无法正确传播运行时错误。(来自维基百科)
使用结构化并发,上述制作晚餐的过程可以通过下面的方式进行:
func makeDinner() async throws -> Meal {// Prepare some variables to receive results from our concurrent child tasksvar veggies: [Vegetable]?var meat: Meat?var oven: Oven?enum CookingStep {case veggies([Vegetable])case meat(Meat)case oven(Oven)}// Create a task group to scope the lifetime of our three child taskstry await withThrowingTaskGroup(of: CookingStep.self) { group ingroup.async {try await .veggies(chopVegetables())}group.async {await .meat(marinateMeat())}group.async {try await .oven(preheatOven(temperature: 350))}for try await finishedStep in group {switch finishedStep {case .veggies(let v): veggies = vcase .meat(let m): meat = mcase .oven(let o): oven = o}}}// If execution resumes normally after `withTaskGroup`, then we can assume// that all child tasks added to the group completed successfully. That means// we can confidently force-unwrap the variables containing the child task// results here.let dish = Dish(ingredients: [veggies!, meat!])return try await oven!.cook(dish, duration: .hours(3))}
Actors
class RiskyCollector {var deck: Set<String>init(deck: Set<String>) {self.deck = deck}func send(card selected: String, to person: RiskyCollector) -> Bool {guard deck.contains(selected) else { return false }deck.remove(selected)person.transfer(card: selected)return true}func transfer(card: String) {deck.insert(card)}}
actor SafeCollector {var deck: Set<String>init(deck: Set<String>) {self.deck = deck}func send(card selected: String, to person: SafeCollector) async -> Bool {guard deck.contains(selected) else { return false }deck.remove(selected)await person.transfer(card: selected)return true}func transfer(card: String) {deck.insert(card)}}
-
Actor 是使用新的 actor 关键字创建的 -
send() 方法被标记为 async,因为它需要异步执行 -
尽管 transfer(card:) 方法没有用 async 标记,但我们仍然需要用 await 调用它,因为它会等到另一个 SafeCollector actor 能够处理请求。
-
两者都是引用类型,因此它们可用于共享状态。 -
它们可以有方法、属性、初始值设定项和下标。 -
它们可以实现协议。任何静态属性和方法在这两种类型中的行为都相同,因为它们没有 self 的概念,因此不会被隔离。
-
Actor 目前不支持继承,这在未来可能会改变 -
所有 Actor 都隐式遵守一个新的 Actor Protocol
更新项 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
不限制 |
|
|
|
|
|
|
|
|
|
DocC
▐ DocC注释编写
/// Eat the provided specialty sloth food.////// Sloths love to eat while they move very slowly through their rainforest/// habitats. They're especially happy to consume leaves and twigs, which they/// digest over long periods of time, mostly while they sleep.////// When they eat food, a sloth's `energyLevel` increases by the food's `energy`.////// - Parameters:/// - food: The food for the sloth to eat./// - quantity: The quantity of the food for the sloth to eat.////// - Returns: The sloth's energy level after eating.////// - Throws: `SlothError.tooMuchFood` if the quantity is more than 100.mutating public func eat(_ food: Food, quantity: Int) throws -> Int {
▐ 从代码注释构建DocC文档
要为 Swift 工程构建文档,请选择Product > Build Documentation。DocC 编译工程的文档并可以在 Xcode 的文档查看器中打开它。
Notifications
在WWDC2021中,系统通知也发生了较大的变化,具体反映在如下几个方面:
▐ 视觉升级
比如用户收到如下通知:
在iOS15系统中开发者可以自定义点击效果,如下图
为了实现上述App icon、内容扩展、动作icon等视觉效果,我们只需要按照下面的方式进行开发:
▐ Focus Mode
Apple 新增了Focus Mode,这个模式可以更好地使通知体验与用户偏好保持一致。
新的专注模式非常适合减少对用户的干扰。iPhone用户可以自定义他们希望收到通知的方式和时间。以前,用户可以通过启用“请勿打扰”模式来选择将所有来电和通知静音。现在,用户将能够通过设置工作、睡眠和个人通知模式来完善他们的通知偏好以适应不同的场景。
对于每个配置文件,用户可以选择要接收通知的应用和联系人、要阻止的应用和联系人,以及要暂停的特定应用功能。用户还可以创建一个主屏幕页面以匹配他们当前的焦点模式并仅显示相关的应用程序。例如,在工作模式下,用户可以选择仅查看与工作相关的应用程序。
焦点配置文件将同步到所有其他苹果设备。 焦点设置也可以由其他设置确定,例如一天中的时间、地理位置或日历事件。
Apple 将使用 AI 自动预测要设置的配置文件。例如,当用户到达工作地点时,iPhone 可以使用地理位置数据来触发工作模式,或者在用户接近就寝时间时使用睡眠时间偏好来触发睡眠模式。
还将有两个与焦点模式相关的新 API。 Status API 告诉应用设备是否处于焦点模式。时间敏感 API 允许应用指定对时间敏感的通知以覆盖设置。
// 返回焦点系统的状态,包括有关当前焦点项目的信息。class func status() -> UIFocusDebuggerOutput// 返回系统通知时间敏感的设置var timeSensitiveSetting: UNNotificationSetting { get }
▐ 通知摘要
用户可以设置对通知进行批处理和优先处理,并选择在一天中的特定时间接收应用程序通知作为摘要。
例如,用户可以将通知分组显示,而不是在整个早上一个接一个地接收通知。
iOS系统将根据用户如何使用不同应用程序而不是应用程序名称和时间来优先处理这些通知。
来自朋友的通知将更接近顶部。带有媒体附件的通知更有可能在摘要中突出显示。
开发人员可以使用新的 relatedScore API 来指示应在此摘要中突出显示应用程序的哪些通知。
/// 系统用于对应用的通知进行排序的权重值var relevanceScore: Double { get }
▐ iOS 通知权限弹框更新
为了支持上面新的功能,权限提示也在发生变化。
现在,当应用程序请求推送权限时,用户将能够指定他们是要立即从应用程序接收通知,还是将通知组合在一起作为通知摘要的一部分。
▐ 通信通知
新系统添加了将应用程序的通知区分为通信通知的功能。
通信通知将包含发送它们的联系人的头像,并且可以与 SiriKit 集成,以便 Siri 可以根据常用联系人智能地提供快捷方式和通信操作建议。
例如,当用户为焦点模式设置允许的联系人或从您的应用拨打电话时,Siri 将根据您的应用程序提供的意图数据智能地推荐联系人。
要使用通信通知,开发者需要在 Xcode 配置中添加通信通知功能,并实现新 UNNotificationContentProviding 协议的 Intent 对象更新应用程序通知服务扩展中通知的内容。
参考资料
https://onesignal.com/blog/ios-notification-changes-updates-from-apples-wwdc-21/
https://developer.apple.com/documentation/Xcode/documenting-a-swift-framework-or-package
https://developer.apple.com/documentation/xcode/writing-symbol-documentation-in-your-source-files
✿ 拓展阅读
本文分享自微信公众号 - 淘系技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。










