Concurrency
并发/异步
原生异步函数
URLSession和DispatchQueue都是异步的, 区别在于URLSession是struct, DispatchQueue是class
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一下, 即await和async 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"
}
}