最新在学习 Swift 并发的一些操作,这里记录一下一些常见的用法和与 Kotlin 协程的一些差异。仅涉及一些简单的基础操作。
目前 Swift 中的异步和线程相关的功能主要分为两套体系:
- GCD (Grand Central Dispatch): 核心类有
DispatchQueue
,DispatchGroup
,DispatchSemaphore
等。 - Swift Concurrency (Swift 5.5+): 新引入的异步模型,使用
Task
,async/await
,Actor
等。
一、主线程 vs 后台线程
1. 切换到主线程
Kotlin 协程:
withContext(Dispatchers.Main) {
// 更新 UI
textView.text = "Hello"
}
Swift (GCD):
DispatchQueue.main.async {
// 更新 UI
self.label.text = "Hello"
}
2. 切换到后台线程
Kotlin 协程:
withContext(Dispatchers.IO) {
val data = fetchData()
}
Swift (GCD):
DispatchQueue.global(qos: .background).async {
let data = fetchData()
// 切回主线程
DispatchQueue.main.async {
self.label.text = "Data loaded"
}
}
DispatchQueue.main
: 主队列(串行队列),可以用于与 UI 交互,类似于 Kotlin 中的Dispatchers.Main
。DispatchQueue.global()
: 全局并发队列(默认优先级为.default
),适合执行非 UI 任务(如网络请求、计算等),多个任务并行执行。DispatchQueue.global(qos: .background)
: 全局并发队列,指定 Quality of Service (QoS) 优先级。其中 Qos 优先级从高到低如下所示:.userInteractive
: 用户交互任务.userInitiated
: 用户触发的任务.default
: 默认优先级.utility
: 后台任务(如下载).background
: 最低优先级
二、并发执行多任务
Kotlin 协程:
private val scope = CoroutineScope(Dispatchers.IO)
val job1 = scope.async { fetchData1() }
val job2 = scope.async { fetchData2() }
val res1 = job1.await()
val res2 = job2.await()
// 或
val resultList = awaitAll(job1, job2)
println("Result: $res1, $res2")
Swift (GCD + DispatchGroup):
let group = DispatchGroup()
var res1: String?
var res2: String?
DispatchQueue.global().async(group: group) {
res1 = fetchData1()
}
DispatchQueue.global().async(group: group) {
res2 = fetchData2()
}
group.notify(queue: .main) {
print("Result: \(res1), \(res2)")
}
DispatchGroup
用于管理一组任务的完成状态,当所有任务完成后触发回调(notify)。
DispatchGroup
的核心方法如下:
enter()
: 标记一个任务开始。leave()
: 标记一个任务完成。notify(queue:execute:)
: 所有任务完成后,在指定队列执行回调。
简单代码示例:
let group = DispatchGroup()
// 任务 1
group.enter()
DispatchQueue.global().async {
fetchData1()
group.leave()
}
// 任务 2
group.enter()
DispatchQueue.global().async {
fetchData2()
group.leave()
}
// 所有任务完成后回调
group.notify(queue: .main) {
print("All done")
}
三、线程安全(线程锁)
Kotlin:
private val lock = Mutex()
private var counter = 0
suspend fun updateCounter() {
lock.withLock {
counter++
}
}
// 或者
private val lock = Any()
fun updateCounter() {
synchronized(lock) {
counter++
}
}
Swift:
private let lock = NSLock()
private var counter = 0
func updateCounter() {
lock.lock()
counter += 1
lock.unlock()
}
// 或者使用 DispatchQueue 作为串行队列
private let queue = DispatchQueue(label: "com.example.queue")
func updateCounter() {
queue.async {
counter += 1
}
}
DispatchQueue
vs NSLock
的使用场景:
NSLock
: 传统的互斥锁,直接控制临界区。轻量级,适合简单、短时间的锁操作。DispatchQueue
: 串行队列,通过串行队列确保任务顺序执行。适合复杂逻辑或需要异步执行的场景。DispatchQueue(label: "test")
: 串行队列,任务按提交顺序依次执行。DispatchQueue.global()
或DispatchQueue(label: "test", attributes: .concurrent)
: 并发队列。
四、async / await
Swift 在 5.5+ 引入了 async/await
机制,下面记录一些简单用法。
1. 定义异步函数
Kotlin (suspend):
suspend fun fetchData(): String {
delay(1000)
return "Data"
}
Swift (async):
func fetchData() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "Data"
}
2. 调用异步函数
Kotlin:
viewModelScope.launch {
val data = fetchData()
textView.text = data
}
Swift (Task):
Task {
let data = await fetchData()
self.label.text = data
}
3. 网络交互示例
下面会使用 Task
、async / await
编写一个简单的网络请求和回调更新 UI 的示例。
// 异步函数,从云端获取数据
func fetchDataFromCloud() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// 异步函数,提交数据到云端
func submitDataToServer(_ data: Data) async throws {
let url = URL(string: "https://api.example.com/submit")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = data
_ = try await URLSession.shared.data(for: request)
}
// 用户点击事件(假设在主线程调用)
func onClick() {
Task {
do {
// 1. 从云端获取数据
let data = try await fetchDataFromCloud()
// 2. 更新 UI(切回主线程)
await MainActor.run {
updateUI(with: data) // 假设的 UI 更新方法
}
// 3. 用户触发提交数据(后台线程)
try await submitDataToServer(data)
// 4. 提交完成后提示用户(主线程)
await MainActor.run {
successAlert()
}
} catch {
await MainActor.run {
errorAlert(error)
}
}
}
}
Task
会自动管理异步任务的执行和线程切换。
await
会挂起当前线程(在例子中非阻塞),直到异步操作完成。
MainActor.run
会确保代码块在主线程中执行(类似 DispatchQueue.main.async
)
五、Actor
Actor
是 Swift 5.5+ 引入的线程安全机制,用于解决数据竞争问题,它的核心是隔离,确保内部状态(属性)只能通过异步方法访问,从而避免并发访问冲突。
Actor
的核心特征如下:
- 隔离访问:Actor 的属性和方法默认只能在 Actor 内部直接访问。
- 串行执行:Actor 内部的任务按顺序执行。
- 编译时检查:编译器会强制要求通过
await
调用 Actor 方法,防止直接并发访问。
下面是一个使用 Actor
的线程安全的计数器例子:
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
return value
}
}
// 使用方式
let counter = Counter()
Task {
// ❌ 错误用例
counter.increment()
// 编译器报错:Expression is 'async' but is not marked with 'await'
// ✅ 正确用例
await counter.increment()
print(await counter.getValue()) // 1
}