- Hands-On Design Patterns with Swift
- Florent Vilmart Giordano Scalzo Sergio De Simone
- 290字
- 2021-07-02 14:45:12
Using groups
Dispatch groups let you organize tasks and execute a block when many tasks have completed. Let's suppose that we need to wait for many long-running tasks to complete, but we don't really know ahead of time how many tasks we need to run, so using a semaphore is not a good solution. For this particular problem, using DispatchGroup is one of the best-suited solutions:
/**
Performs some work on any thread
- parameter done: A block that will be called when the work is done
- note: This method may not be thread safe
*/
func doWork(done: @escaping () -> ()) {
/* complex implementation doing important things */
}
Consider the previous function, which could be provided by a third-party SDK or your own API. It performs some work, and, at a later point, the callback, done will be called.
Now, let's suppose that we need to execute it a certain number of times; we could write it as follows:
// completion block
let complete = {
print("DONE!")
}
// initialize a count for the remaining operations
var count = 0
for i in 0..<4 {
count += 1 // add one operation
doWork {
count -= 1 // one operation is done
if count == 0 { // yay! we're finished
complete()
}
}
}
This is very, very bad code, for many reasons, including the following:
- Poor readability
- Unsafe access to the count variable from many threads
- Not reusable
For all of these issues, use DispatchGroup, as follows:
let group = DispatchGroup()
// Iterate through all our tasks
for i in 1..<4 {
// tell the group we're adding additional work
group.enter()
// Do the piece of work
doWork {
// tell the group the work is done
group.leave()
}
}
// tell the group to call complete when done
group.notify(queue: .main, execute: complete)
As you can see, this approach has many benefits:
- The code is more readable and easier to follow
- There is no unsafe incrementation of variables
- You have better control over the execution of your completion block
- It allows for higher order abstractions
Let's take a look at an abstraction over DispatchGroup that you can use in your projects to synchronize many executions together:
// Typealiases so it's easier to reference them all
typealias Block = () -> ()
typealias FunctionWithCallback = (@escaping Block) -> ()
/**
Runs asynchronous functions and calls completion when all is done
- parameter functions: List of functions to run
- parameter completion: A block to call when all functions have completed
*/
func runAll(functions: [FunctionWithCallback], completion: @escaping Block) {
// Create a group
let group = DispatchGroup()
functions.forEach { (function) in
group.enter()
function {
group.leave()
}
}
group.notify(queue: .main, execute: completion)
}
In the preceding example, we created a very high abstraction over simple invocations that complete in the future; thanks to DispatchGroup, this implementation is thread safe, easy to understand and maintain, and highly reusable.
- 數據要素安全流通
- 大數據技術基礎
- 輕松學大數據挖掘:算法、場景與數據產品
- Java Data Science Cookbook
- Libgdx Cross/platform Game Development Cookbook
- 大數據營銷:如何讓營銷更具吸引力
- 大數據架構和算法實現之路:電商系統的技術實戰
- Proxmox VE超融合集群實踐真傳
- 云原生數據中臺:架構、方法論與實踐
- 探索新型智庫發展之路:藍迪國際智庫報告·2015(下冊)
- 數據科學實戰指南
- Google Cloud Platform for Developers
- MySQL技術內幕:SQL編程
- 區塊鏈技術應用與實踐案例
- 聯動Oracle:設計思想、架構實現與AWR報告