如何在Swift中使用AsyncStream创建类似回调的行

如何在Swift中使用AsyncStream创建类似回调的行为

hudson 译 原文

毫无疑问,Swift并发彻底改变了我们在Swift中处理异步代码的方式。它的一个强大组件是AsyncStream,这是一种特殊的AsyncSequence形式,非常适合使用async/await 语法实现回调或类似委托的行为。

在Swift 并发之前,开发人员必须依靠闭包来触发回调,并在异步操作期间通知调用者某些事件。然而,随着AsyncStream的引入,这种基于闭包的方法现在可以被更直观、更直接的async/await语法所取代。

在本文中,让我们探索一个简单而说明性的示例,说明如何利用AsyncStream来跟踪下载操作的进度。阅读完成后,您将很好地了解AsyncStream的工作原理,并开始在自己的项目中使用它。

所以,不用多说,让我们开始吧。

示例应用程序

为了展示AsyncStream的力量,让我们创建一个示例应用程序,该应用程序将模拟下载操作,并使用进度条显示下载进度。

在这里插入图片描述

为了模拟通常与文件下载相关的等待期,我创建了一个带有 performDownload() 方法的File结构,该方法将随机睡眠一段时间。

1
2
3
4
5
6
7
8
9
10
11
swift复制代码struct File {

let name: String

func performDownload() async {

// Sleep for a random amount of time to emulate the wait required to download a file
let downloadTime = Double.random(in: 0.03...0.5)
try? await Task.sleep(for: .seconds(downloadTime))
}
}

在现实生活中,这种performDownload() 方法很可能由连接到服务器并等待其响应的代码组成。

有了这个解释,让我们深入研究有趣的部分。

创建异步流

首先,让我们创建一个下载器类(FileDownloader),该类接受File文件对象数组并逐个下载,每次成功下载后,它将通过提供下载文件的文件名来通知调用者。

为了实现这种行为,基于闭包的方法很可能看起来像这样:

1
2
3
4
5
6
swift复制代码static func download(_ files: [File], completion: (String) -> Void) {

// Download each file and trigger completion handler
// ...
// ...
}

然而,如果我们选择async/await语法,我们将需要用AsyncStream替换完成处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
swift复制代码static func download(_ files: [File]) -> AsyncStream<String> {

// Init AsyncStream with element type = `String`
let stream = AsyncStream(String.self) { continuation in

// Perform download operation and yield the downloaded file’s filename
// ...
// ...
}

return stream
}

如上述代码所示,我们可以通过给它一个元素类型和自定义闭包来初始化AsyncStream,该闭包将元素交给AsyncStream。在我们的案例中,我们将元素类型设置为String,因为每次下载成功时,我们的闭包将产生下载文件的文件名。

有了这些,我们可以像这样实现执行下载操作的自定义闭包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
swift复制代码// Init AsyncStream with element type = `String`
let stream = AsyncStream(String.self) { continuation in

Task {
for file in files {

// Download the file
await file.performDownload()

// Yield the element (filename) when download is completed
continuation.yield(file.name)
}

// All files are downloaded
// Call the continuation’s finish() method when there are no further elements to produce
continuation.finish()
}
}

使用AsyncStream时要记住的一个要点是在完成所有操作后调用延续的finish() 方法。这一步骤至关重要,因为如果不这样做,将导致在调用点无限期等待,导致我们应用程序中的意外和非预期的行为。

消费AsyncStream

有了FileDownloader,是时候将其与用户界面集成以显示下载进度了。首先,我们将创建50个File对象并触发下载过程。

1
2
3
4
5
6
7
swift复制代码let totalFile = 50

// Generate file objects
let files = (1...totalFile).map { File(name: “Image_\($0).jpg”) }

// Start download
let downloaderStream = FileDownloader.download(files)

现在,为了在UI上显示下载进度,我们将利用给定的AsyncStream实例(downloaderStream),并利用for-wait-in语法处理每个文件名,文件名是在调用延续的yield()方法时由 AsyncStream生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
swift复制代码Task {
var downloadedFile = 0
for await filename in downloaderStream {

downloadedFile += 1

// Update progress bar
progressBar.progress = Float(downloadedFile) / Float(totalFile)

// Update status label
statusLabel.text = “Downloaded \(filename)”
}

statusLabel.text = “Download completed”
}

如前所述,调用延续的 finish() 方法是一个必不可少的步骤。如果没有此步骤,for循环将无限期等待,状态消息将不会更改为“下载完成”。

如果您想亲自尝试一下,您可以在GitHub上找到完整的示例代码。

感谢您的阅读。👨🏻‍💻

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%