Swift使用dispatchsemaphore限制并发网络请求

    科技2025-02-16  35

    In this article, we will learn how to limit the number of ongoing network requests using a DispatchSemaphore.

    在本文中,我们将学习如何使用DispatchSemaphore限制正在进行的网络请求的数量。

    Limiting resource-heavy operations allows us to manage thread resources more efficiently. For example, when you have a task to simultaneously fetch ten high-quality images, it is useful to allow the execution of only one or two image loading operations at a time.

    限制资源密集型操作可以使我们更有效地管理线程资源。 例如,当您有一个任务要同时获取十个高质量图像时,一次仅执行一个或两个图像加载操作非常有用。

    In short, this is what you will master in this tutorial:

    简而言之,这是您将在本教程中掌握的内容:

    Executing tasks on a background DispatchQueue.

    在后台DispatchQueue上执行任务。

    Leveraging a DispatchSemaphore and setting the max number of concurrent operations.

    利用DispatchSemaphore并设置最大并发操作数。

    Understanding the difference between using sync and async executions.

    了解使用sync执行和async执行之间的区别。

    开始吧 (Let’s Start)

    First, let’s create a URL, a background DispatchQueue, and a DispatchSemaphore with a value of 2:

    首先,我们创建一个URL ,一个背景DispatchQueue和一个DispatchSemaphore ,其值为2:

    import UIKit let url = URL(string: "https://source.unsplash.com/random/5120x1000")! let queue = DispatchQueue.global(qos: .background) let semaphore = DispatchSemaphore(value: 2)

    The value parameter of the semaphore indicates the maximum number of concurrent operations allowed. In other words, we will allow only two images to be fetched at the same time.

    信号量的value参数表示允许的最大并发操作数。 换句话说,我们将只允许同时提取两个图像。

    Now, let’s add a method called loadRandomPhoto() and create an async task:

    现在,让我们添加一个名为loadRandomPhoto()的方法并创建一个async任务:

    import UIKit let url = URL(string: "https://source.unsplash.com/random/5120x1000")! let queue = DispatchQueue.global(qos: .background) let semaphore = DispatchSemaphore(value: 2) func loadRandomPhoto() { queue.async { } }

    Add semaphore.wait() and semaphore.signal() inside the task body, as follows:

    在任务主体内添加semaphore.wait()和semaphore.signal() ,如下所示:

    import UIKit let url = URL(string: "https://source.unsplash.com/random/5120x1000")! let queue = DispatchQueue.global(qos: .background) let semaphore = DispatchSemaphore(value: 2) func loadRandomPhoto() { queue.async { semaphore.wait() defer { semaphore.signal() } } }

    The semaphore.wait() command decrements the semaphore counter by 1. We have set the counter to 2 when initializing the DispatchSemaphore, so once the loadRandomPhoto() function is run, the value is decreased by 1.

    semaphore.wait()命令将信号量计数器减 1。在初始化DispatchSemaphore ,我们已将计数器设置为2,因此一旦运行loadRandomPhoto()函数,该value减1.。

    The semaphore.signal() command increases the counter of the semaphore, meaning that an operation is finished and another one can take its place.

    semaphore.signal()命令增加了信号量的计数器,这意味着一个操作已完成,另一个可以代替它。

    We want to run the semaphore.signal() command at the end of the async task. The defer statement conveniently achieves that, so we don’t need to worry about inserting the command in the wrong place.

    我们要在async任务结束时运行semaphore.signal()命令。 defer语句可以方便地实现这一点,因此我们不必担心将命令插入错误的位置。

    Now let’s fetch Data from the url property:

    现在,让我们从url属性中获取Data :

    import UIKit let url = URL(string: "https://source.unsplash.com/random/5120x1000")! let queue = DispatchQueue.global(qos: .background) let semaphore = DispatchSemaphore(value: 2) func loadRandomPhoto() { queue.async { semaphore.wait() defer { semaphore.signal() } if let data = try? Data(contentsOf: url) { if let image = UIImage(data: data) { print(image) } } } }

    For debugging purposes, we simply print the UIImage obtained from Data.

    出于调试目的,我们仅打印从Data获得的UIImage 。

    Great! Now we have to launch several image loading operations and observe the output:

    大! 现在,我们必须启动一些图像加载操作并观察输出:

    import UIKit let url = URL(string: "https://source.unsplash.com/random/5120x1000")! let queue = DispatchQueue.global(qos: .background) let semaphore = DispatchSemaphore(value: 2) func loadRandomPhoto() { queue.async { semaphore.wait() defer { semaphore.signal() } if let data = try? Data(contentsOf: url) { if let image = UIImage(data: data) { print(image) } } } } for _ in 0...9 { loadRandomPhoto() }

    As we can see, the for loop fires the loadRandomPhoto() function ten times.

    如我们所见, for循环会触发loadRandomPhoto()函loadRandomPhoto() 。

    Now, if we run the Xcode Playground, we will see images printed in batches of two:

    现在,如果我们运行Xcode Playground,我们将看到分两批打印的图像:

    The semaphores are done, but what’s the difference between using a sync execution and an async one?

    信号量已完成,但是使用sync执行和async执行有什么区别?

    When using sync, the thread on which it was called will be blocked until the task is finished, while an async execution doesn’t wait for a task to complete. Therefore, it doesn’t block the current thread.

    使用sync ,调用它的线程将被阻塞,直到任务完成为止,而async执行不等待任务完成。 因此,它不会阻止当前线程。

    In our example, let’s remove the DispatchSemaphore and change the async execution to a sync one:

    在我们的例子中,我们删除了DispatchSemaphore ,改变async执行的sync之一:

    import UIKit let url = URL(string: "https://source.unsplash.com/random/5120x1000")! let queue = DispatchQueue.global(qos: .background) func loadRandomPhoto() { queue.sync { if let data = try? Data(contentsOf: url) { if let image = UIImage(data: data) { print(image) } } } } for _ in 0...9 { loadRandomPhoto() }

    As a result, we see images loading only one at a time:

    结果,我们看到图像一次只能加载一张:

    So, using sync produces the same result when using a DispatchSemaphore with a value set to 1.

    因此,使用DispatchSemaphore并将value设置为1时,使用sync会产生相同的结果。

    However, in our case above, even though we specified the .background quality of service of the DispatchQueue, the sync call still happens on the main thread. As a result, we have the image loading operations freezing our UI until they are finished.

    但是,在上述情况下,即使我们指定了DispatchQueue的.background服务质量, sync调用仍会在主线程上进行。 结果,我们使图像加载操作冻结了UI,直到完成它们。

    Therefore, it’s better to be very careful when calling sync on the DispatchQueue.main. If we call it from the main thread, the app will terminate as a result of deadlock. On the other hand, when it is called from the background thread, we should avoid adding to it a time-consuming block of code to prevent the UI from freezing.

    因此,最好在DispatchQueue.main上调用sync时要非常小心。 如果我们从主线程调用它,则该应用将由于死锁而终止。 另一方面,当从后台线程调用它时,我们应避免向其添加一个耗时的代码块以防止UI冻结。

    结语 (Wrapping Up)

    I hope you found this tutorial helpful. Thanks for reading!

    希望本教程对您有所帮助。 谢谢阅读!

    翻译自: https://medium.com/better-programming/limit-concurrent-network-requests-with-dispatchsemaphore-in-swift-f313afd938c6

    相关资源:ios-swift nsurlsession实现并发网络请求.zip
    Processed: 0.010, SQL: 8