Swift5.五、DocC、Notifications,蘋果WWDC21帶來的最大技術變化

WWDC (蘋果開發者大會) 2021已經落下帷幕,今年的WWDC 提供了 200 多個深度課程,以幫助開發者瞭解WWDC2021 引入的新技術,本文會幫國內開發者梳理部分WWDC 2021帶來的技術上的變化。
ios


Swift5.5git


WWDC2021 給咱們帶來了Swift 5.5,這是Swift 語言最新的版本,在這個版本中有許多重大的更新,下面會你們詳細介紹一下Swift 5.5的一些重要更新。
github


  Swift Concurrency


Swift 5.5 中最大的更新就是引入了全新的併發編程方式,包括async/await語法、結構化併發、Actor等,新的併發編程方式解決了咱們以往使用回調的種種缺陷 (嵌套地獄、回調錯誤處理麻煩、回調分支編寫困難等),爲開發者帶來了極大的便利。web


  • async/await


過去咱們編寫異步代碼都是經過回調的方式,以下:express


func processImageData2a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) { loadWebResource("dataprofile.txt") { dataResource, error in guard let dataResource = dataResource else { completionBlock(nil, error) return } loadWebResource("imagedata.dat") { imageResource, error in guard let imageResource = imageResource else { completionBlock(nil, error) return } decodeImage(dataResource, imageResource) { imageTmp, error in guard let imageTmp = imageTmp else { completionBlock(nil, error) return } dewarpAndCleanupImage(imageTmp) { imageResult, error in guard let imageResult = imageResult else { completionBlock(nil, error) return } completionBlock(imageResult) } } } }}
processImageData2a { image, error in guard let image = image else { display("No image today", error) return } display(image)}


經過回調的方式編寫異步代碼有如下缺點:編程


  • 閱讀不直觀canvas

  • 嵌套邏輯複雜swift

  • 錯誤處理麻煩xcode

  • 分支邏輯難以處理安全

  • 常常會忘了回調或者返回


在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 -> Image
func 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 tasks var 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 tasks try await withThrowingTaskGroup(of: CookingStep.self) { group in group.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 = v case .meat(let m): meat = m case .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))}


上述代碼中chopVegetables、marinateMeat 和preheatOven 將併發運行,而且可能以任何順序進行。

不管哪一種狀況,任務組都會天然地將狀態從子任務傳播到父任務;在這個例子中,若是菜刀發生了事故,chopVegetables() 函數可能會拋出一個錯誤。

拋出的錯誤完成了切菜的子任務。正如預期的那樣,該錯誤隨後將傳播到 makeDinner() 函數以外。在出現此錯誤退出 makeDinner() 函數的主體時,任何還沒有完成的子任務(醃肉或預熱烤箱,可能二者)將自動取消。

結構化併發意味着咱們沒必要手動傳播錯誤和管理取消;若是在調用 withTaskGroup 後繼續正常執行,咱們能夠假設它的全部子任務都成功完成。


  • Actors


Swift 5.5引入了Actor,它在概念上相似於在併發環境中能夠安全使用的類。Swift 確保在任何給定時間只能由單個線程訪問 Actor 內的可變狀態,這有助於在編譯器級別消除各類嚴重的錯誤。

咱們能夠先一塊兒看一個Swift中的Class,以下:


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) }}


RiskyCollector 在單線程環境中,代碼是安全的。然而,在多線程環境中,咱們的代碼存在潛在的多線程競爭未做處理。

Actor 經過引入 Actor 隔離解決了這個問題:除非異步執行,不然沒法從 Actor 對象外部讀取屬性和方法,而且根本沒法從 Actor 對象外部寫入屬性。異步行爲不是爲了性能;相反,這是由於 Swift 會自動將這些請求放入一個按順序處理的隊列中,以免出現多線程競爭。

咱們可使用Actor從新實現一個SafeCollector,以下:


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 可以處理請求。

須要明確的是,actor 能夠自由地、異步或以其餘方式使用本身的屬性和方法,可是當與不一樣的 actor 交互時,它必須始終異步完成。經過這些特性,Swift 能夠確保永遠不會同時訪問全部與 actor 隔離的狀態,更重要的是,這是在編譯時完成的,以保證線程安全。

Actor 和 Class 有一些類似之處:

  • 二者都是引用類型,所以它們可用於共享狀態。
  • 它們能夠有方法、屬性、初始值設定項和下標。
  • 它們能夠實現協議。任何靜態屬性和方法在這兩種類型中的行爲都相同,由於它們沒有 self 的概念,所以不會被隔離。

除了 Actor 隔離以外,Actor 和 Class之間還有另外兩個重要的區別:

  • Actor 目前不支持繼承,這在將來可能會改變
  • 全部 Actor 都隱式遵照一個新的 Actor Protocol

除了上述特性外,Swift 5.5 還增長了很多新特性,可是Cocurrency部分的新特性只有iOS15及以上系統能夠運行,下面整理了一個表格,列出了Swift 5.5的重要更新以及適用的系統:


更新項

描述連接
適用系統
async/awai
https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md
iOS15及以上
async sequences
https://github.com/apple/swift-evolution/blob/main/proposals/0298-asyncsequence.md
iOS15及以上
Effectful read-only properties
https://github.com/apple/swift-evolution/blob/main/proposals/0310-effectful-readonly-properties.md
iOS15及以上
Structured Concurrency
https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md
iOS15及以上
async let
https://github.com/apple/swift-evolution/blob/main/proposals/0317-async-let.md
iOS15及以上
Continuations for interfacing async tasks with synchronous code
https://github.com/apple/swift-evolution/blob/main/proposals/0300-continuation.md
iOS15及以上
Actors
https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md
iOS15及以上
Global Actors
https://github.com/apple/swift-evolution/blob/main/proposals/0316-global-actors.md
iOS15及以上
Sendable and @Sendable closures
https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md
不限制
#if for postfix member expressions
https://github.com/apple/swift-evolution/blob/main/proposals/0308-postfix-if-config-expressions.md
不限制
Codable synthesis for enums with associated values
https://github.com/apple/swift-evolution/blob/main/proposals/0295-codable-synthesis-for-enums-with-associated-values.md
不限制
lazy now works in local contexts

不限制

Extend Property Wrappers to Function and Closure Parameters
https://github.com/apple/swift-evolution/blob/main/proposals/0293-extend-property-wrappers-to-function-and-closure-parameters.md
不限制
Extending Static Member Lookup in Generic Contexts
https://github.com/apple/swift-evolution/blob/main/proposals/0299-extend-generic-static-member-lookup.md
不限制


DocC 


DocC是Xcode13包含的文檔編譯器,它能夠幫助開發者輕鬆地爲 Swift 代碼生成文檔。

編譯器經過將您在源代碼中編寫的註釋與在 Xcode 項目中一塊兒存在的擴展文件、文章和其餘資源相結合來構建文檔,從而能夠爲開發人員建立豐富且引人入勝的文檔。 

使用 DocC,開發者能夠提供技術參考和示例的組合,並使用強大的組織和連接功能將它們鏈接在一塊兒。

編譯器直接與 Xcode 集成以整合現有的工做流程,包括代碼提示、快速幫助等。

並且由於直接在源代碼中編寫文檔,因此可使用現有的工具(例如 Git)來跟蹤全部的變動。


  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註釋編寫規範能夠參考文檔: https://developer.apple.com/documentation/xcode/writing-symbol-documentation-in-your-source-files

  從代碼註釋構建DocC文檔


爲了讓 DocC 編譯文檔,Xcode 首先構建 Swift 工程,並在編譯的同時存儲有關其 API 的信息。DocC 使用該信息將註釋編譯爲 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索