Swift Concurrency 學習筆記
Swift Concurrency 學習筆記
Swift 5.5裡新增了Swift Concurrency,語法和Web前端裡的非同步非常之像,語法學習起來比較簡單。
基本使用
關鍵詞就是async
和await
。不同的是需要放入Task
裡執行,並且一定需要加await
關鍵字。
```swift func fn() async { print("async function") }
Task { await fn() } ```
另外一種是可以丟擲錯誤的async
函式。
swift
func fn() async throws -> String{
throw URLError(.badURL)
return "async"
}
呼叫會丟擲錯誤的async
函式的時候需要使用try
關鍵字。
swift
Task{
let result = try await fn()
print(result)
}
這樣是不會輸入任何結果的,因為已經丟擲錯誤了,在這種情況需要用do-catch
語句。
swift
Task {
do {
let result = try await fn()
print(result)
} catch {
print(error.localizedDescription) // 輸出了錯誤資訊
}
}
使用do-catch
可以捕獲錯誤,另外還有2種try
的修飾,try!
和try?
,可以不使用do-catch
。
swift
let result = try! await fn() // 程式會直接崩潰,不會走do-catch,捕獲不了錯誤
print(result)
try!
是非常不建議使用的。
swift
let result = try? await fn() // 報錯會返回nil
print(result) // nil
try?
在出現錯誤的時候會返回nil
,在不需要捕獲具體錯誤資訊的時候非常有用。
Task
Task
接受一個閉包作為引數,返回一個例項。
取消 Task
Task
會返回例項,通過該例項的cancel()
方法可取消任務。
```swift func fn() async { try? await Task.sleep(for: .seconds(2)) print("async function") }
let task = Task { await fn() } task.cancel() ```
但是實際我們還是會輸出"async function",只是跳過了等待2秒。
所以我們需要呼叫Task.isCancelled
或者Task.checkCancellation()
來確保不再執行。
swift
func fn() async {
try? await Task.sleep(for: .seconds(2))
if Task.isCancelled { return }
print("async function")
}
Task的優先順序
Task
中有優先順序的概念
Swift
Task(priority: .background) {
print("background: \(Task.currentPriority)")
}
Task(priority: .high) {
print("high: \(Task.currentPriority)")
}
Task(priority: .low) {
print("low: \(Task.currentPriority)")
}
Task(priority: .medium) {
print("medium: \(Task.currentPriority)")
}
Task(priority: .userInitiated) {
print("userInitiated: \(Task.currentPriority)")
}
Task(priority: .utility) {
print("utility: \(Task.currentPriority)")
}
輸出
medium: TaskPriority(rawValue: 21)
high: TaskPriority(rawValue: 25)
low: TaskPriority(rawValue: 17)
userInitiated: TaskPriority(rawValue: 25)
utility: TaskPriority(rawValue: 17)
background: TaskPriority(rawValue: 9)
優先順序並不一定匹配,有時候會有優先順序提升的情況。
子任務會繼承父任務的優先順序。
swift
Task(priority: .high) {
Task {
print(Task.currentPriority) // TaskPriority(rawValue: 25)
}
}
通過Task.detached
來分離任務。
swift
Task(priority: .high) {
Task.detached {
print(Task.currentPriority) // TaskPriority(rawValue: 21)
}
}
掛起Task
Task.yield()
可以掛起當前任務。
```swift Task { print("task 1") } Task { print("task 2") }
// 輸出 // task 1 // task 2 ```
使用Task.yield()
。
swift
Task {
await Task.yield()
print("task 1")
}
Task {
print("task 2")
}
// 輸出
// task 2
// task 1
async let
await
是阻塞的,意味著當前await
函式在沒執行完之前是不會執行下一行的。
```swift func fn() async -> String { try? await Task.sleep(for: .seconds(2)) return "async function" }
Task { let result = await fn() print(result) // 等待兩秒後輸出async function } ```
有些情況需要並行執行多個async
函式,這個時候則會用到async let
。
```swift Task { async let fn1 = fn() async let fn2 = fn()
let result = await [fn1, fn2]
print(result) // ["async function", "async function"]
} ```
TaskGroup
如果任務過多,或者是迴圈裡建立並行任務,async let
就不是那麼得心應手了,這種情況我們應該使用withTaskGroup
和withThrowingTaskGroup
。
swift
Task {
let string = await withTaskGroup(of: Int.self) { group in
for i in 0 ... 10 {
group.addTask {
try? await Task.sleep(for: .seconds(2))
return i
}
}
var collected = [Int]()
for await value in group {
collected.append(value)
}
return collected
}
print(string)
}
of
為子任務返回型別,在TaskGroup
裡我們也能通過group.cancelAll()
和group.isCanceled
配合來取消任務。
Continuations
Continuations
用於將以前的非同步回撥函式變成async
函式,類似前端裡的new Promise(resolve,reject)
。
現有以下程式碼
swift
func fn(_ cb: @escaping (String) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
cb("completed")
}
}
這段程式碼是通過@escaping
閉包的形式來獲取結果,不能通過await
獲取,只需要使用withCheckedContinuation
就可以將函式改造為async
函式。
```swift func asyncFn() async -> String { await withCheckedContinuation { continuation in fn { continuation.resume(returning: $0) } } }
Task { let result = await asyncFn() print(result) } ```
除了withCheckedContinuation
,還有withCheckedThrowingContinuation
可以丟擲錯誤。
actor
在很多語言裡,都有執行緒鎖這個概念,避免多個執行緒同一時間訪問同一資料,造成錯誤。
Swift Concurrency裡通過actor
來解決這個問題。actor
裡的屬性和方法都是執行緒安全的。
```swift actor MyActor { var value:String = "test"
func printValue(){
print(value)
}
} ```
actor
內預設屬性和方法都是非同步的,需要通過await
來呼叫。
swift
Task {
let myActor = MyActor()
await myActor.printValue()
print(await myActor.value)
}
如果需要某個方法不用await
呼叫,需要使用nonisolated
關鍵字。
```swift actor MyActor { nonisolated func nonisolatedFn(){ print("nonisolated") } }
let myActor = MyActor() myActor.nonisolatedFn() ```
MainActor
現有以下程式碼
```swift class VM: ObservableObject { @Published var value = "value"
func change() {
Task{
try? await Task.sleep(for:.seconds(2))
self.value = "change"
}
}
}
Text(vm.value) .onTapGesture { vm.change() } ```
當點選Text
兩秒後會修改值。這時候會提示。
[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates
因為UI改動都應該發生在主執行緒,可以使用老辦法Dispatch.main.async
來解決。在Swift Concurrency裡有多個方法。
swift
func change() {
Task {
try? await Task.sleep(for: .seconds(2))
await MainActor.run{
self.value = "change"
}
}
}
或者
swift
func change() {
Task {@MainActor in
try? await Task.sleep(for: .seconds(2))
self.value = "change"
}
}
也可以使用@MainActor
將方法或者類標記執行在主佇列。
SwiftUI中使用
SwiftUI中直接.task
修飾符即可。
swift
Text("Hello World 🌍")
.task {
await fn()
}
同時有一點比較好的是在onDisappear
的時候會自動取消Task
。
結語
作為初學者,Swift Concurrency簡化了很多非同步相關的問題,不需要再去使用閉包了,不會造成回撥地獄,結合SwiftUI使用比Combine更簡單友好,非常不錯。
最近幾天學習了這個,雖然我陽了,但是還是頂著發燒總結一晚上,以免燒完已經不記得了。