详解Swift中 Sendable AnyActor Actor GlobalActor MainActor Task、await、async
详解Swift中 Sendable AnyActor Actor GlobalActor MainActor 的关联或者关系 及其 各自的作用 和 用法 以及与 Task、await、async:
- Sendable 协议
作用:
Sendable 是一个协议,它用于标记可以安全地跨线程或异步任务传递的数据类型。符合 Sendable 协议的类型可以在并发环境中被安全地传递,避免因数据竞争而产生的问题。
使用场景:
如果你希望将某个对象安全地传递给其他任务(如 Task),那么这个对象就必须符合 Sendable 协议。
在并发环境下,Swift 会确保 Sendable 类型的对象不会在多个线程之间产生数据竞争。
示例:
struct MyStruct: Sendable {
var value: Int
}
在上面的例子中,MyStruct 遵循了 Sendable 协议,因此它可以被安全地传递给多个并发任务。
Sendable 与 Task、async、await 的关系:
Task 会在不同的并发上下文中执行。如果你需要将某些数据从一个任务传递到另一个任务中,这些数据就需要符合 Sendable 协议。
await 和 async 会创建异步任务,如果传递的数据不符合 Sendable,编译器会报错,提示数据类型不是可发送的。
- Actor 类型
作用:
Actor 是一种新的引用类型,专门用于确保在并发环境中的数据安全。Actor 通过序列化对其内部状态的访问来避免数据竞争。它通过自动同步访问来保证多线程环境中的线程安全。
使用场景:
Actor 主要用于封装需要在并发环境下访问的状态或数据,它确保数据不会被多个线程同时修改。
Actor 会保护其内部状态,所有对 Actor 内部数据的访问都会被序列化,因此可以避免数据竞态和不一致的状态。
示例:
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
return value
}
}
let counter = Counter()
Task {
await counter.increment()
let currentValue = await counter.getValue()
print(currentValue)
}
Actor 与 Sendable 的关系:
Actor 本身是 线程安全 的,但如果 Actor 内部持有非 Sendable 类型的对象,它就不能跨线程或任务传递该对象。
如果你将 Actor 的实例作为任务的一部分进行传递,它需要符合 Sendable 协议,这意味着 Actor 中的所有状态和数据也需要是 Sendable 的。
- AnyActor 类型
作用:
AnyActor 是一个类型擦除的包装器,允许你将某个具体的 Actor 类型转为通用的 Actor 类型。它通常用于在无法预先知道具体 Actor 类型的场景中,或者当你需要在函数或方法中处理不同类型的 Actor 时。
使用场景:
当你需要处理多个不同类型的 Actor,并且无法在编译时确定具体的类型时,AnyActor 就非常有用。
示例:
actor MyActor {
var value: Int = 0
}
func performAction(actor: AnyActor) {
// 对 AnyActor 做一些操作
}
- GlobalActor 协议
作用:
GlobalActor 是一个协议,它允许你为全局共享的并发环境(如主线程或后台线程)指定一个特定的执行上下文。通过 GlobalActor,你可以确保某些代码始终在特定的线程或调度队列上执行。
使用场景:
例如,MainActor 是一个 GlobalActor,它确保某些代码在 主线程 上执行,通常用于 UI 更新操作。
示例:
@globalActor
struct MainActor: GlobalActor {
static let shared = MainActor()
}
@MainActor
func updateUI() {
// 此方法会在主线程上执行
}
- MainActor 类型
作用:
MainActor 是一个特殊的 GlobalActor,它确保标记的代码总是在主线程上执行。由于 UI 更新通常必须在主线程上进行,因此 MainActor 主要用于确保 UI 更新代码运行在主线程。
使用场景:
UI更新:在 SwiftUI 或 UIKit 中,需要确保界面更新代码运行在主线程,因此可以用 @MainActor 属性来标记这些方法。
示例:
@MainActor
func updateUI() {
// 只有在主线程中执行
print("UI is updated!")
}
Task {
await updateUI() // 确保在主线程执行
}
Task、async 和 await 与上述概念的关系
async 和 await:async 用于标记异步函数,await 用于等待异步函数的结果。在并发环境中,你可以通过 async 和 await 来执行任务,而不阻塞线程。
Task:Task 用于创建一个并发任务,它允许你在后台执行异步代码。你可以通过 Task 来启动异步代码并使用 await 来等待它的结果。
Actor 和 MainActor:当你在 Task 中使用 await 时,如果你的任务需要访问某个 Actor 类型的数据,确保你使用正确的并发上下文。例如,访问 UI 相关的状态时,要确保它在主线程上执行,可以使用 @MainActor 标记。
综合示例:
@MainActor
actor ViewModel {
var data: String = "Hello"
func updateData() {
data = "Updated"
print("Data updated on the main thread")
}
}
func performAsyncWork() async {
let viewModel = ViewModel()
await viewModel.updateData() // 在主线程执行
}
Task {
await performAsyncWork()
}
总结:
Sendable 是用于标记可以安全地传递的数据类型。
Actor 用于封装并发访问的对象,保证数据安全。
AnyActor 用于类型擦除,使得不同类型的 Actor 可以通用处理。
GlobalActor 和 MainActor 用于指定代码执行的全局并发上下文,确保代码在特定的线程上执行。
Task、async 和 await 用于创建和管理异步任务。