如何在Swift?中使用?async?let?并發(fā)運(yùn)行后臺(tái)任務(wù)
前言
Async/await 語(yǔ)法是在 Swift 5.5 引入的,在 WWDC 2021中的 Meet async/await in Swift 對(duì)齊進(jìn)行了介紹。它是編寫異步代碼的一種更可讀的方式,比調(diào)度隊(duì)列和回調(diào)函數(shù)更容易理解。Async/await 語(yǔ)法與其他編程語(yǔ)言(如 C# 或 JavaScript)中使用的語(yǔ)法類似。使用 "async let "是為了并行的運(yùn)行多個(gè)后臺(tái)任務(wù),并等待它們的綜合結(jié)果。
Swift 異步編程是一種編寫允許某些任務(wù)并發(fā)運(yùn)行而不是按順序運(yùn)行的代碼的方法。這可以提高應(yīng)用程序的性能,允許它同時(shí)執(zhí)行多個(gè)任務(wù),但更重要的是,它可以用來(lái)確保用戶界面對(duì)用戶輸入的響應(yīng),同時(shí)任務(wù)在后臺(tái)線程上執(zhí)行。
長(zhǎng)期運(yùn)行的任務(wù)阻塞了UI
在一個(gè)同步的程序中,代碼以線性的、從上到下的方式運(yùn)行。程序等待當(dāng)前任務(wù)完成后再進(jìn)入下一任務(wù)。這在用戶界面(UI)方面會(huì)產(chǎn)生問題,因?yàn)槿绻粋€(gè)長(zhǎng)期運(yùn)行的任務(wù)被同步執(zhí)行,程序就會(huì)阻塞,UI就會(huì)變得沒有反應(yīng),直到任務(wù)完成。
下面的代碼模擬了一個(gè)長(zhǎng)期運(yùn)行的任務(wù),如以同步方式下載一個(gè)文件,其結(jié)果是UI 變得沒有反應(yīng),直到任務(wù)完成。這樣的用戶體驗(yàn)是不可接受的。
Model:
struct DataFile : Identifiable, Equatable { var id: Int var fileSize: Int var downloadedSize = 0 var isDownloading = false init(id: Int, fileSize: Int) { self.id = id self.fileSize = fileSize } var progress: Double { return Double(self.downloadedSize) / Double(self.fileSize) } mutating func increment() { if downloadedSize < fileSize { downloadedSize += 1 } } }
ViewModel:
class DataFileViewModel: ObservableObject { @Published private(set) var file: DataFile init() { self.file = DataFile(id: 1, fileSize: 10) } func downloadFile() { file.isDownloading = true for _ in 0..<file.fileSize { file.increment() usleep(300000) } file.isDownloading = false } func reset() { self.file = DataFile(id: 1, fileSize: 10) } }
View:
struct TestView1: View { @ObservedObject private var dataFiles: DataFileViewModel init() { dataFiles = DataFileViewModel() } var body: some View { VStack { /// 從文末源代碼獲取其實(shí)現(xiàn) TitleView(title: ["Synchronous"]) Button("Download All") { dataFiles.downloadFile() } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.file.isDownloading) HStack(spacing: 10) { Text("File 1:") ProgressView(value: dataFiles.file.progress) .frame(width: 180) Text("\((dataFiles.file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if dataFiles.file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } .padding() Spacer().frame(height: 200) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() } }
使用 async/await 在后臺(tái)執(zhí)行任務(wù)
將 ViewModel 中的downloadFile
方法修改為異步的。請(qǐng)注意,由于DataFile
模型是被視圖監(jiān)聽的,對(duì)模型的任何改變都需要在UI線程上執(zhí)行。這是通過(guò)使用 MainActor 隊(duì)列來(lái)完成的,即用MainActor.run
包裹所有的模型更新。
ViewModel
class DataFileViewModel2: ObservableObject { @Published private(set) var file: DataFile init() { self.file = DataFile(id: 1, fileSize: 10) } func downloadFile() async -> Int { await MainActor.run { file.isDownloading = true } for _ in 0..<file.fileSize { await MainActor.run { file.increment() } usleep(300000) } await MainActor.run { file.isDownloading = false } return 1 } func reset() { self.file = DataFile(id: 1, fileSize: 10) } }
View:
struct TestView2: View { @ObservedObject private var dataFiles: DataFileViewModel2 @State var fileCount = 0 init() { dataFiles = DataFileViewModel2() } var body: some View { VStack { TitleView(title: ["Asynchronous"]) Button("Download All") { Task { let num = await dataFiles.downloadFile() fileCount += num } } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.file.isDownloading) Text("Files Downloaded: \(fileCount)") HStack(spacing: 10) { Text("File 1:") ProgressView(value: dataFiles.file.progress) .frame(width: 180) Text("\((dataFiles.file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if dataFiles.file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } .padding() Spacer().frame(height: 200) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() } }
在后臺(tái)執(zhí)行多個(gè)任務(wù)
現(xiàn)在我們有一個(gè)文件在后臺(tái)下載,UI顯示進(jìn)度,讓我們把它改為多個(gè)文件。ViewModel
被改為持有一個(gè)DataFiles
數(shù)組,而不是一個(gè)單一的文件。添加一個(gè)downloadFiles
方法來(lái)遍歷所有文件并下載每一個(gè)。
視圖被綁定到DataFiles
數(shù)組,并更新顯示每個(gè)文件的下載進(jìn)度。下載按鈕被綁定到異步的downloadFiles
中。
ViewModel:
class DataFileViewModel3: ObservableObject { @Published private(set) var files: [DataFile] @Published private(set) var fileCount = 0 init() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] } var isDownloading : Bool { files.filter { $0.isDownloading }.count > 0 } func downloadFiles() async { for index in files.indices { let num = await downloadFile(index) await MainActor.run { fileCount += num } } } private func downloadFile(_ index: Array<DataFile>.Index) async -> Int { await MainActor.run { files[index].isDownloading = true } for _ in 0..<files[index].fileSize { await MainActor.run { files[index].increment() } usleep(300000) } await MainActor.run { files[index].isDownloading = false } return 1 } func reset() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] } }
View:
struct TestView3: View { @ObservedObject private var dataFiles: DataFileViewModel3 init() { dataFiles = DataFileViewModel3() } var body: some View { VStack { TitleView(title: ["Asynchronous", "(multiple Files)"]) Button("Download All") { Task { await dataFiles.downloadFiles() } } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.isDownloading) Text("Files Downloaded: \(dataFiles.fileCount)") ForEach(dataFiles.files) { file in HStack(spacing: 10) { Text("File \(file.id):") ProgressView(value: file.progress) .frame(width: 180) Text("\((file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } } .padding() Spacer().frame(height: 150) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() } }
使用 "async let " 下載多個(gè)文件
使用 "async let "來(lái)模擬并發(fā)下載多個(gè)文件的情況
上面的代碼可以被改進(jìn),以并行地執(zhí)行多個(gè)下載,因?yàn)槊總€(gè)任務(wù)都是獨(dú)立于其他任務(wù)的。在Swift并發(fā)中,這是用async let
實(shí)現(xiàn)的,它用一個(gè)承諾立即給一個(gè)變量賦值,允許代碼執(zhí)行下一行代碼。然后,代碼等待這些承諾,等待最終結(jié)果的完成。
async/await:
func downloadFiles() async { for index in files.indices { let num = await downloadFile(index) await MainActor.run { fileCount += num } } }
async let
func downloadFiles() async { async let num1 = await downloadFile(0) async let num2 = await downloadFile(1) async let num3 = await downloadFile(2) let (result1, result2, result3) = await (num1, num2, num3) await MainActor.run { fileCount = result1 + result2 + result3 } }
ViewModel
class DataFileViewModel4: ObservableObject { @Published private(set) var files: [DataFile] @Published private(set) var fileCount = 0 init() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] } var isDownloading : Bool { files.filter { $0.isDownloading }.count > 0 } func downloadFiles() async { async let num1 = await downloadFile(0) async let num2 = await downloadFile(1) async let num3 = await downloadFile(2) let (result1, result2, result3) = await (num1, num2, num3) await MainActor.run { fileCount = result1 + result2 + result3 } } private func downloadFile(_ index: Array<DataFile>.Index) async -> Int { await MainActor.run { files[index].isDownloading = true } for _ in 0..<files[index].fileSize { await MainActor.run { files[index].increment() } usleep(300000) } await MainActor.run { files[index].isDownloading = false } return 1 } func reset() { files = [ DataFile(id: 1, fileSize: 10), DataFile(id: 2, fileSize: 20), DataFile(id: 3, fileSize: 5) ] } }
View
struct TestView4: View { @ObservedObject private var dataFiles: DataFileViewModel4 init() { dataFiles = DataFileViewModel4() } var body: some View { VStack { TitleView(title: ["Parallel", "(multiple Files)"]) Button("Download All") { Task { await dataFiles.downloadFiles() } } .buttonStyle(BlueButtonStyle()) .disabled(dataFiles.isDownloading) Text("Files Downloaded: \(dataFiles.fileCount)") ForEach(dataFiles.files) { file in HStack(spacing: 10) { Text("File \(file.id):") ProgressView(value: file.progress) .frame(width: 180) Text("\((file.progress * 100), specifier: "%0.0F")%") ZStack { Color.clear .frame(width: 30, height: 30) if file.isDownloading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .blue)) } } } } .padding() Spacer().frame(height: 150) Button("Reset") { dataFiles.reset() } .buttonStyle(BlueButtonStyle()) Spacer() } .padding() } }
結(jié)論
在后臺(tái)執(zhí)行長(zhǎng)期運(yùn)行的任務(wù)并保持UI的響應(yīng)是很重要的。async/await提供了一個(gè)干凈的機(jī)制來(lái)執(zhí)行異步任務(wù)。有的時(shí)候,一個(gè)方法在后臺(tái)調(diào)用多個(gè)方法,默認(rèn)情況下是按順序進(jìn)行這些調(diào)用。async 讓其立即返回,允許代碼進(jìn)行下一個(gè)調(diào)用,然后所有返回的對(duì)象可以一起等待。這使得多個(gè)后臺(tái)任務(wù)可以并行進(jìn)行。
到此這篇關(guān)于在 Swift 中使用 async let 并發(fā)運(yùn)行后臺(tái)任務(wù)的文章就介紹到這了,更多相關(guān)Swift async let 后臺(tái)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Swift 3.0將UILabel數(shù)字顏色設(shè)置為紅色的方法
這篇文章主要介紹了關(guān)于在Swift中將UILabel數(shù)字顏色設(shè)置為紅色的方法,文中給出了詳細(xì)的示例代碼,相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-03-03Swift?并發(fā)修改Sendable?閉包實(shí)例詳解
這篇文章主要為大家介紹了Swift?并發(fā)修改Sendable?閉包實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10swift 錯(cuò)誤處理do catch try try!使用詳解
這篇文章主要介紹了swift 錯(cuò)誤處理do catch try try!使用詳解的相關(guān)資料,需要的朋友可以參考下2023-03-03Swift中通知中心(NotificationCenter)的使用示例
這篇文章主要給大家介紹了關(guān)于Swift中通知中心(NotificationCenter)使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10詳解Swift中的數(shù)據(jù)類型類型轉(zhuǎn)換
Swift中的類型轉(zhuǎn)換可以結(jié)合類的繼承等面向?qū)ο蟮木幊烫匦詠?lái)進(jìn)行,本文中我們就來(lái)詳解Swift中的數(shù)據(jù)類型類型轉(zhuǎn)換,需要的朋友可以參考下2016-07-07