Swift Concurrency 学习笔记

Swift 5.5里新增了Swift Concurrency,语法和Web前端里的异步十分之像,语法学习起来比较简单。

基本运用

关键词便是asyncawait。不同的是需求放入Task里履行,并且必定需求加await关键字。

func fn() async {
    print("async function")
}
Task {
    await fn()
}

别的一种是能够抛出过错的async函数。

func fn() async throws -> String{
    throw URLError(.badURL)
    return "async"
}

调用会抛出过错的async函数的时分需求运用try关键字。

Task{
    let result = try await fn()
    print(result)
}

这样是不会输入任何结果的,因为现已抛出过错了,在这种情况需求用do-catch句子。

Task {
    do {
        let result = try await fn()
        print(result)
    } catch {
        print(error.localizedDescription) // 输出了过错信息
    }
}

运用do-catch能够捕获过错,别的还有2种try的修饰,try!try?,能够不运用do-catch

let result = try! await fn() // 程序会直接溃散,不会走do-catch,捕获不了过错
print(result)

try!是十分不建议运用的。

let result = try? await fn() // 报错会回来nil
print(result) // nil

try?在呈现过错的时分会回来nil,在不需求捕获详细过错信息的时分十分有用。

Task

Task承受一个闭包作为参数,回来一个实例。

撤销 Task

Task会回来实例,经过该实例的cancel()办法可撤销使命。

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()来保证不再履行。

func fn() async {
    try? await Task.sleep(for: .seconds(2))
    if Task.isCancelled { return }
    print("async function")
}

Task的优先级

Task中有优先级的概念

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)

优先级并不必定匹配,有时分会有优先级提升的情况。

子使命会继承父使命的优先级。

Task(priority: .high) {
    Task {
        print(Task.currentPriority) // TaskPriority(rawValue: 25)
    }
}

经过Task.detached来别离使命。

Task(priority: .high) {
    Task.detached {
        print(Task.currentPriority) // TaskPriority(rawValue: 21)
    }
}

挂起Task

Task.yield()能够挂起当时使命。

Task {
    print("task 1")
}
Task {
    print("task 2")
}
// 输出
// task 1
// task 2

运用Task.yield()

Task {
    await Task.yield()
    print("task 1")
}
Task {
    print("task 2")
}
// 输出
// task 2
// task 1

async let

await是堵塞的,意味着当时await函数在没履行完之前是不会履行下一行的。

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

Task {
    async let fn1 = fn()
    async let fn2 = fn()
    let result = await [fn1, fn2]
    print(result) // ["async function", "async function"]
}

TaskGroup

假如使命过多,或许是循环里创建并行使命,async let就不是那么称心如意了,这种情况咱们应该运用withTaskGroupwithThrowingTaskGroup

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)

现有以下代码

func fn(_ cb: @escaping (String) -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        cb("completed")
    }
}

这段代码是经过@escaping闭包的方式来获取结果,不能经过await获取,只需求运用withCheckedContinuation就能够将函数改造为async函数。

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里的属性和办法都是线程安全的。

actor MyActor {
    var value:String = "test"
    func printValue(){
        print(value)
    }
}

actor内默许属性和办法都是异步的,需求经过await来调用。

Task {
    let myActor = MyActor()
    await myActor.printValue()
    print(await myActor.value)
}

假如需求某个办法不用await调用,需求运用nonisolated关键字。

actor MyActor {
    nonisolated func nonisolatedFn(){
        print("nonisolated")
    }
}
let myActor = MyActor()
myActor.nonisolatedFn()

MainActor

现有以下代码

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里有多个办法。

func change() {
    Task {
        try? await Task.sleep(for: .seconds(2))
        await MainActor.run{
            self.value = "change"
        }
    }
}

或许

    func change() {
        Task {@MainActor in
            try? await Task.sleep(for: .seconds(2))
            self.value = "change"
        }
    }

也能够运用@MainActor将办法或许类符号运行在主队列。

SwiftUI中运用

SwiftUI中直接.task修饰符即可。

Text("Hello World ")
    .task {
        await fn()
    }

同时有一点比较好的是在onDisappear的时分会自动撤销Task

结语

作为初学者,Swift Concurrency简化了许多异步相关的问题,不需求再去运用闭包了,不会造成回调阴间,结合SwiftUI运用比Combine更简单友爱,十分不错。

最近几天学习了这个,尽管我阳了,但是仍是顶着发烧总结一晚上,避免烧完现已不记得了。