官术网_书友最值得收藏!

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.

主站蜘蛛池模板: 丰都县| 陇川县| 格尔木市| 阳泉市| 新民市| 扎赉特旗| 阿荣旗| 广灵县| 通许县| 高碑店市| 丁青县| 永丰县| 奉化市| 奇台县| 鲁山县| 桐乡市| 南岸区| 历史| 武平县| 德保县| 武安市| 九台市| 江津市| 武山县| 中西区| 绥滨县| 大冶市| 文山县| 精河县| 囊谦县| 浮梁县| 修水县| 新兴县| 沂南县| 福清市| 吴江市| 双鸭山市| 进贤县| 彩票| 大化| 竹北市|