文章目录
前言
Async/await 语法是在 Swift 5.5 引入的,在 WWDC 2021中的 Meet async/await in Swift 对齐进行了介绍。它是编写异步代码的一种更可读的方式,比调度队列和回调函数更容易理解。Async/await 语法与其他编程语言(如 C# 或 JavaScript)中使用的语法类似。使用 "async let "是为了并行的运行多个后台任务,并等待它们的综合结果。
Swift 异步编程是一种编写允许某些任务并发运行而不是按顺序运行的代码的方法。这可以提高应用程序的性能,允许它同时执行多个任务,但更重要的是,它可以用来确保用户界面对用户输入的响应,同时任务在后台线程上执行。
长期运行的任务阻塞了UI
在一个同步的程序中,代码以线性的、从上到下的方式运行。程序等待当前任务完成后再进入下一任务。这在用户界面(UI)方面会产生问题,因为如果一个长期运行的任务被同步执行,程序就会阻塞,UI就会变得没有反应,直到任务完成。
下面的代码模拟了一个长期运行的任务,如以同步方式下载一个文件,其结果是UI 变得没有反应,直到任务完成。这样的用户体验是不可接受的。
Model:
structDataFile:Identifiable,Equatable{var id:Intvar fileSize:Intvar downloadedSize =0var isDownloading =falseinit(id:Int, fileSize:Int){self.id = id
self.fileSize = fileSize
}var progress:Double{returnDouble(self.downloadedSize)/Double(self.fileSize)}mutatingfuncincrement(){if downloadedSize < fileSize {
downloadedSize +=1}}}
ViewModel:
classDataFileViewModel:ObservableObject{@Publishedprivate(set)var file:DataFileinit(){self.file =DataFile(id:1, fileSize:10)}funcdownloadFile(){
file.isDownloading =truefor_in0..<file.fileSize {
file.increment()usleep(300000)}
file.isDownloading =false}funcreset(){self.file =DataFile(id:1, fileSize:10)}}
View:
structTestView1:View{@ObservedObjectprivatevar dataFiles:DataFileViewModelinit(){
dataFiles =DataFileViewModel()}var body:someView{VStack{/// 从文末源代码获取其实现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 在后台执行任务
将 ViewModel 中的
downloadFile
方法修改为异步的。请注意,由于
DataFile
模型是被视图监听的,对模型的任何改变都需要在UI线程上执行。这是通过使用 MainActor 队列来完成的,即用
MainActor.run
包裹所有的模型更新。
ViewModel
classDataFileViewModel2:ObservableObject{@Publishedprivate(set)var file:DataFileinit(){self.file =DataFile(id:1, fileSize:10)}funcdownloadFile()async->Int{awaitMainActor.run {
file.isDownloading =true}for_in0..<file.fileSize {awaitMainActor.run {
file.increment()}usleep(300000)}awaitMainActor.run {
file.isDownloading =false}return1}funcreset(){self.file =DataFile(id:1, fileSize:10)}}
View:
structTestView2:View{@ObservedObjectprivatevar dataFiles:DataFileViewModel2@Statevar fileCount =0init(){
dataFiles =DataFileViewModel2()}var body:someView{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()}}
在后台执行多个任务
现在我们有一个文件在后台下载,UI显示进度,让我们把它改为多个文件。
ViewModel
被改为持有一个
DataFiles
数组,而不是一个单一的文件。添加一个
downloadFiles
方法来遍历所有文件并下载每一个。
视图被绑定到
DataFiles
数组,并更新显示每个文件的下载进度。下载按钮被绑定到异步的
downloadFiles
中。
ViewModel:
classDataFileViewModel3:ObservableObject{@Publishedprivate(set)var files:[DataFile]@Publishedprivate(set)var fileCount =0init(){
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}funcdownloadFiles()async{for index in files.indices {let num =awaitdownloadFile(index)awaitMainActor.run {
fileCount += num
}}}privatefuncdownloadFile(_ index:Array<DataFile>.Index)async->Int{awaitMainActor.run {
files[index].isDownloading =true}for_in0..<files[index].fileSize {awaitMainActor.run {
files[index].increment()}usleep(300000)}awaitMainActor.run {
files[index].isDownloading =false}return1}funcreset(){
files =[DataFile(id:1, fileSize:10),DataFile(id:2, fileSize:20),DataFile(id:3, fileSize:5)]}}
View:
structTestView3:View{@ObservedObjectprivatevar dataFiles:DataFileViewModel3init(){
dataFiles =DataFileViewModel3()}var body:someView{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 inHStack(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 " 下载多个文件
使用 "async let "来模拟并发下载多个文件的情况
上面的代码可以被改进,以并行地执行多个下载,因为每个任务都是独立于其他任务的。在Swift并发中,这是用
async let
实现的,它用一个承诺立即给一个变量赋值,允许代码执行下一行代码。然后,代码等待这些承诺,等待最终结果的完成。
async/await:
funcdownloadFiles()async{for index in files.indices {let num =awaitdownloadFile(index)awaitMainActor.run {
fileCount += num
}}}
async let
funcdownloadFiles()async{asynclet num1 =awaitdownloadFile(0)asynclet num2 =awaitdownloadFile(1)asynclet num3 =awaitdownloadFile(2)let(result1, result2, result3)=await(num1, num2, num3)awaitMainActor.run {
fileCount = result1 + result2 + result3
}}
ViewModel
classDataFileViewModel4:ObservableObject{@Publishedprivate(set)var files:[DataFile]@Publishedprivate(set)var fileCount =0init(){
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}funcdownloadFiles()async{asynclet num1 =awaitdownloadFile(0)asynclet num2 =awaitdownloadFile(1)asynclet num3 =awaitdownloadFile(2)let(result1, result2, result3)=await(num1, num2, num3)awaitMainActor.run {
fileCount = result1 + result2 + result3
}}privatefuncdownloadFile(_ index:Array<DataFile>.Index)async->Int{awaitMainActor.run {
files[index].isDownloading =true}for_in0..<files[index].fileSize {awaitMainActor.run {
files[index].increment()}usleep(300000)}awaitMainActor.run {
files[index].isDownloading =false}return1}funcreset(){
files =[DataFile(id:1, fileSize:10),DataFile(id:2, fileSize:20),DataFile(id:3, fileSize:5)]}}
View
structTestView4:View{@ObservedObjectprivatevar dataFiles:DataFileViewModel4init(){
dataFiles =DataFileViewModel4()}var body:someView{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 inHStack(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()}}
结论
在后台执行长期运行的任务并保持UI的响应是很重要的。async/await提供了一个干净的机制来执行异步任务。有的时候,一个方法在后台调用多个方法,默认情况下是按顺序进行这些调用。async 让其立即返回,允许代码进行下一个调用,然后所有返回的对象可以一起等待。这使得多个后台任务可以并行进行。
版权归原作者 Swift社区 所有, 如有侵权,请联系我们删除。