CompletionRequirement

public class CompletionRequirement<T>

A type that handles completion callbacks with safety checks and semantics that reduce the risks of callers forgetting to call the completion handler.

The type is not concurrency safe (see notes in Threading) and it will always call the completion handler synchronously, using the supplied completionQueue if available, or inline on whatever the current queue thread is.

To use, define a typealias for this type, with T the type of the completion function’s argument (use a tuple if your completion requires multiple arguments).

Then make your function that requires a completion handler take an instance of this type instead of the closure type, and make the function expect a return value of the nested Status type:

protocol MyCoordinator {
  typealias DoSomethingCompletion = CompletionRequirement<Bool>

  func doSomething(input: Any, completionRequirement: DoSomethingCompletion) -> DoSomethingCompletion.Status
}

Now, when calling this function on the protocol, you construct the requirement instance, pass it and verify the result:

let coordinator: MyCoordinator = ...
let completion = MyCoordinator.DoSomethingCompletion( { (shouldCancel: Bool, completedAsync: Bool) in
   print("Cancel? \(shouldCancel)")
})

The block takes one argument of type `T`, in this case a boolean, and a second `Bool` argument that indicates
if the completion block has been called asynchronously.

// Call the function that requires completion
let status = coordinator.doSomething(input: x, completionRequirement: completion)

// Make sure one of the valid statuses was returned.
// This safety test ensures that the completion from the correct completion requirement instance was returned.
precondition(completion.verify(status))

// If the result does not return true for `isCompletingAsync`, the completion callback will have already been called by now.
if !status.isCompletingAsync {
    print("Completed synchronously: \(status.value)")
} else {
    print("Completing asynchronously... see you later")
}

When implementing such a function requiring a completion handler, you return one of two statuses returned by either the CompletionRequirement.completed(_ arg: T) or CompletionRequirement.willCompleteAsync(). The CompletionRequirement will take care of calling the completion block as appropriate.

func doSomething(input: Any, completionRequirement: DoSomethingCompletion) -> DoSomethingCompletion.Status {
    return completionRequirement.completedSync(false)
}

// or for async completion, you retain the result and later call `completed(value)`

func doSomething(input: Any, completionRequirement: DoSomethingCompletion) -> DoSomethingCompletion.Status {
    // Capture the async status
    let result = completionRequirement.willCompleteAsync()
    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
        // Use the retained status to indicate completion later
        result.completed(false)
    }
    return result
}

## Threading

A `CompletionRequirement` is not concurrency safe. You must not change any properties or call any methods
after calling `completedSync` or `willCompleteAsync`. State of the object will not change asynchronously once
you have called either of these.