It is not often when we need to wrap an async function with a completion handler. Typically, the reverse is what happens. This need can happen in codebases where the public interface can’t change just right now, but internally it is moving towards async-await functions. Let’s jump in and see how to wrap an async function, an async throwing function and an async throwing function what returns a value.

To illustrate how to use it, we’ll see an example of how a PhotoEffectApplier type has a public interface consisting of completion handler based functions and how it internally uses PhotoProcessor type what only has async functions. The end result looks like this:

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters struct PhotoProcessor { func process(_ photo: Photo) async throws -> Photo { // … return Photo(name: UUID().uuidString) } func setConfiguration(_ configuration: Configuration) async throws { // … } func cancel() async { // … } } public final class PhotoEffectApplier { private let processor = PhotoProcessor() public func apply(effect: PhotoEffect, to photo: Photo, completion: @escaping (Result<Photo, Error>) -> Void) { Task(operation: { try await self.processor.process(photo) }, completion: completion) } public func setConfiguration(_ configuration: Configuration, completion: @escaping (Error?) -> Void) { Task(operation: { try await self.processor.setConfiguration(configuration) }, completion: completion) } public func cancel(completion: @escaping (Error?) -> Void) { Task(operation: { await self.processor.cancel() }, completion: completion) } } view raw PhotoEffectApplier.swift hosted with ❤ by GitHub

In this example, we have all the interested function types covered: async, async throwing and async throwing with a return type. Great, but let’s have a look at these Task initializers what make this happen. The core idea is to create a Task, run an operation, and then make a completion handler callback. Since most of the time we need to run the completion on the main thread, then we have a queue argument with the default queue set to the main thread.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters extension Task { @discardableResult init<T>( priority: TaskPriority? = nil, operation: @escaping () async throws -> T, queue: DispatchQueue = .main, completion: @escaping (Result<T, Failure>) -> Void ) where Success == Void, Failure == any Error { self.init(priority: priority) { do { let value = try await operation() queue.async { completion(.success(value)) } } catch { queue.async { completion(.failure(error)) } } } } } view raw AsyncThrowsValue.swift hosted with ❤ by GitHub This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters extension Task { @discardableResult init( priority: TaskPriority? = nil, operation: @escaping () async throws -> Void, queue: DispatchQueue = .main, completion: @escaping (Error?) -> Void ) where Success == Void, Failure == any Error { self.init(priority: priority) { do { try await operation() queue.async { completion(nil) } } catch { queue.async { completion(error) } } } } } view raw AsyncThrows.swift hosted with ❤ by GitHub This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters extension Task { @discardableResult init( priority: TaskPriority? = nil, operation: @escaping () async -> Void, queue: DispatchQueue = .main, completion: @escaping () -> Void ) where Success == Void, Failure == Never { self.init(priority: priority) { await operation() queue.async { completion() } } } } view raw Async.swift hosted with ❤ by GitHub

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Support me on Patreon Donate with Paypal Buy me a coffee