一、前言
WWDC21上,蘋果宣佈了Swift5.5新特性——併發!從此在語言層面而非庫的層面完成了對併發(異步與並行)的支持。這是一個大喜訊!
在早期進行多線程編髮時,是十分困難的,需要手動處理線程的創建、銷燬、線程間的同步。在iOS編程領域,蘋果很早就引入了NSThread、GCD、NSOperation,這大幅降低了多線程編程的難度。但是,這些都是通過系統庫來實現的,沒有語法層面的支持,還是比較容易引起問題的。日常碰到的疑難問題大多數都與多線程相關,特別是多線程引起的內存問題,就更難處理。
幸運的是——Swift5.5在語法層面支持了併發(多線程)的編程。這是一個巨大的進步!因爲可以通過編譯器的檢查幫助我們規避由於我們疏忽、對併發的不瞭解等原因導致的問題。
二、併發
2.1 定義和調用異步函數
// 在函數、方法的右小括號後面添加關鍵字async,則標明此函數是一個異步函數(可能會被吊起、恢復)
func listPhotos(inGallery name: String) async -> [String] {
let result = // ... some asynchronous networking code ...
return result
}
// 在調用併發函數的時候需要添加關鍵字await
// 就和調用可能拋出異常的函數時添加try關鍵字相同
let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[1]
let photo = await downloadPhoto(named: name)
show(photo)
異步函數或方法不是在任何地方都可以調用的,只有在以下幾個場景方可調用:
- 在異步的函數、方法、屬性內部可以調用衣服函數或方法
- 在一個分離的子任務之中
- 被@main標識的結構體、類、枚舉所定義的靜態main方法之中
2.2 異步序列
// 在此for循環之中,每次獲得下一個元素時都是異步的
let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
print(line)
}
// 自定義的序列類型只要符合AsyncSequence協議就可以使用異步方法
2.3 並行調用異步方法
// 異步執行此方法,當前代碼被掛起
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)
// ----------------------------------------
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)
2.4 Task與Tash Group
async let firstPhoto = downloadPhoto(named: photoNames[0]) // 隱式創建了一個task
async let secondPhoto = downloadPhoto(named: photoNames[1]) // 隱式創建了一個task
async let thirdPhoto = downloadPhoto(named: photoNames[2]) // 隱式創建了一個task
// 所有的異步代碼都屬於某個task
// 通過明確創建Task Group可以管理多個task之間的關係
// Task 與 Task Group,就如同爲對GCD進行封裝的NSOperation
await withTaskGroup(of: Data.self) { taskGroup in
let photoNames = await listPhotos(inGallery: "Summer Vacation")
for name in photoNames {
taskGroup.async { await downloadPhoto(named: name) }
}
}
// 有明確的父子關係的task就屬於結構化的
// 沒有明確父子關係的task就屬於非結構化的,其靈活性更大,但是開發者承擔更多的責任
三、Actors
簡單理解actor 就是滿足併發要求的class!
簡單理解,就是對actor屬性的訪問,默認加鎖了的。
actor是引用類型。
actor TemperatureLogger {
let label: String
var measurements: [Int]
private(set) var max: Int
init(label: String, measurement: Int) {
self.label = label
self.measurements = [measurement]
self.max = measurement
}
}
let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(await logger.max) // 需要添加await標識
// 以下代碼屬於actor的一部分,所以不需要添加await
extension TemperatureLogger {
func update(with measurement: Int) {
measurements.append(measurement)
if measurement > max {
max = measurement
}
}
}