Skip to content

Instantly share code, notes, and snippets.

@pwc3
Created April 29, 2020 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pwc3/b3fad646f33e7ea7827244b0764fc8b1 to your computer and use it in GitHub Desktop.
Save pwc3/b3fad646f33e7ea7827244b0764fc8b1 to your computer and use it in GitHub Desktop.
Value Type Capture
import Foundation
class SimulatedOperation {
// Simulated function that collects data from multiple async calls.
// Build an array of [String] values, one element per async call. Hit the
// completion block once all of the elements are populated.
func collectData(completion: @escaping ([String]) -> Void) {
// Locally accumulate the results here. Why does this work?
//
// `Array` is a value type (struct). I expected the value to be
// captured when the closure was created, meaning:
//
// 1.) Modifying the array from a closure (passed to `getString` below)
// would not propagate back to the original variable.
// 2.) The closure passed to DispatchGroup.notify() would capture the
// array before all of the simulated work was done.
//
// Neither of those assumptions are correct!
//
// In Swift, captured variables are evaluated at the time of closure
// execution. In effect, it captures a reference (or pointer) to the
// variable.
//
// As a result, you can modify captured values in closures.
var result: [String] = []
// Kick off some number of async operations. Use a DispatchGroup to get
// notified when all of the operations are done.
let group = DispatchGroup()
for i in 0..<100 {
group.enter()
getString(for: i) {
// We get called back on a serial queue, so we don't have to
// worry about concurrent access to `result`.
result.append($0)
group.leave()
}
}
// When all of the tasks in `group` are complete, use
// DispatchGroup.notify() to call our completion argument.
group.notify(queue: .main) {
completion(result)
}
}
// Serial background queue for simulated work.
let background = DispatchQueue(label: "queue", qos: .background)
// Do some simulated long-lasting work via DispatchQueue.asyncAfter.
func getString(for int: Int, completion: @escaping (String) -> Void) {
background.asyncAfter(deadline: .now() + 1) {
completion("\(int)")
}
}
}
// Make sure we get back and print the correct number of elements in the array.
let op = SimulatedOperation()
op.collectData {
print("Result: \($0)")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment