Concurrency

并发/异步

原生异步函数

URLSessionDispatchQueue都是异步的, 区别在于URLSessionstruct, DispatchQueueclass

func loadSignature() async throws -> String? {
  let (data, _) = try await URLSession.shared.data(from: someURL)
  return String(data: data, encoding: .utf8)
}

Task

对于同步函数来说,线程决定了它的执行环境。而对于异步函数,则由任务 (Task) 决定执行环境。

let newPhoto = // ... 图片数据 ...
let handle = Task {
	return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
let result = await handle.value

等待多个异步方法的技巧

等待多个异步方法的技巧, 传统写法:

let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])

let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

这样一次只能download一个, 改成如下:

async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])

let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

几个download可以并行, 也因为如此, 需要在使用的时候await一下, 即awaitasync let都是调用异步函数的方法 延伸阅读: TaskGroup

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

把回调改为async函数

类似前端的new Promise(resolve, reject) 原代码:

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

withCheckedContinuation改造:

func asyncFn() async -> String {
    await withCheckedContinuation { continuation in
        fn { continuation.resume(returning: $0) }
    }
}

Task {
  let result = await asyncFn()
  print(result)
}

Actor

actor里的操作都是线程安全的, 并且全是异步的, 如果需要不异步使用, 则要加nonisolated关键字

actor MyActor {
    var value:String = "test"
    
    func printValue(){
        print(value)
    }   
    nonisolated func nonisolatedFn(){
        print("nonisolated")
    }
}
// 正常调用
Task {
    let myActor = MyActor()
    await myActor.printValue()
    print(await myActor.value)
}
// 同步调用
let myActor = MyActor()
myActor.nonisolatedFn()

MainActor

工作在主线程的actor. 以下把文本框绑定了点击事件, 由异步代码来更新文本框的值(通过监斩 vm.value), 这时候设置value等同于设置UI, 所以会报错:

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

两种改法:

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