AddAsync is a Swift Macro that automatically generates async/await versions of your legacy completion-handler functions.
Stop writing boilerplate withCheckedContinuation wrappers manually. Let the compiler do it for you safely and cleanly.
- ✅ Zero Runtime Overhead: All code is generated at compile time.
- ✅ Protocol Support: Works on protocol definitions (generates async signatures without bodies).
- ✅ Smart Detection: Automatically detects if the function should throw errors (for
Resulttypes) or just return values. - ✅ Generic Support: Works perfectly with
<T: Model>, arrays[T], and complex signatures.
- Go to File > Add Package Dependencies...
- Enter the URL of your repository:
https://github.com/theLastOrbit/swift-macros-AddAsync.git - Click Add Package.
Add it to your dependencies in Package.swift:
dependencies: [
.package(url: "https://github.com/theLastOrbit/swift-macros-AddAsync.git", from: "1.1.0")
]Then add "AddAsync" to your target's dependencies.
Simply attach @AddAsync to any function that accepts a completion handler as its last parameter.
If your completion handler returns a Result<T, Error>, the generated function will be async throws and return the success type T.
Code:
import AddAsync
@AddAsync
func fetchUser(id: String, completion: @escaping (Result<User, Error>) -> Void) {
// Legacy code logic...
}Generated Peer Function (Invisible):
func fetchUser(id: String) async throws -> User {
return try await withCheckedThrowingContinuation { continuation in
fetchUser(id: id) { result in
continuation.resume(with: result)
}
}
}You can use @AddAsync in protocols. It will generate the async requirement signature automatically.
Code:
protocol NetworkService {
@AddAsync
func fetchConfig(completion: @escaping (Result<Config, Error>) -> Void)
}Generated Requirement (Invisible):
protocol NetworkService {
func fetchConfig(completion: @escaping (Result<Config, Error>) -> Void)
// Generated:
func fetchConfig() async throws -> Config
}If your completion handler returns a standard value (like T? or [T]?), the generated function will be async (non-throwing) and preserve all generic constraints.
Code:
@AddAsync
func fetch<T: Model>(with router: BaseRouter, completion: @escaping (T?) -> Void) {
// Legacy code logic...
}Generated Peer Function (Invisible):
func fetch<T: Model>(with router: BaseRouter) async -> T? {
return await withCheckedContinuation { continuation in
fetch(with: router) { value in
continuation.resume(returning: value)
}
}
}- Swift 5.9+ (Xcode 15+)
- iOS 13.0+ / macOS 10.15+ (Backward compatible runtime)
This library is released under the MIT License. See LICENSE for details.
Inspiration: Got the inspiration to make this library:
- From this Linkedin post of Hirra Salim
- Also from this awesome git repo