Use modern async/await
in legacy synchronous code and migrate at your own pace.
AwaitlessKit
provides Swift macros to automatically generate synchronous wrappers for your async
functions, making it easy to call new async APIs from existing nonasync code. This helps you gradually adopt async/await without breaking old APIs or rewriting everything at once.
- Quick Start
- Why AwaitlessKit?
- Requirements
- Installation
- Core Features
- Quick Examples
- Migration Guide
- License
- Credits
import AwaitlessKit
class DataService {
@Awaitless
func fetchUser(id: String) async throws -> User {
let response = try await URLSession.shared.data(from: userURL(id))
return try JSONDecoder().decode(User.self, from: response.0)
}
// Automatically generates a noasync counterpart:
// @available(*, noasync) func fetchUser(id: String) throws -> User {
// try Noasync.run({
// try await fetchUser(id: id)
// })
// }
}
// Use both versions during migration
let service = DataService()
let user1 = try await service.fetchUser(id: "123") // Async version
let user2 = try service.fetchUser(id: "456") // Generated sync version
The Problem: Swift's async/await adoption is an "all-or-nothing" proposition. You can't easily call async functions from sync contexts, making incremental migration painful.
The Solution: AwaitlessKit
automatically generates synchronous counterparts for your async functions, allowing you to:
- ✅ Migrate to
async/await
incrementally - ✅ Maintain backward compatibility during transitions
- ✅ Avoid rewriting entire call chains at once
- ✅ Keep existing APIs stable while modernizing internals
⚠️ Important: This library intentionally bypasses Swift's concurrency safety mechanisms. Use during migration periods only, not as a permanent solution.
Swift Version | Xcode Version | Support Level |
---|---|---|
Swift 6.0+ | Xcode 16+ | ✅ Full support |
Swift 5.9+ | Xcode 15+ | #awaitless() unavailable) |
Swift 5.8- | Xcode 14- | ❌ Not supported |
Recommended: Xcode 16 with Swift 6.0 for the best experience.
Add to your Package.swift
:
dependencies: [
.package(url: "https://github.com/bonkey/AwaitlessKit.git", from: "6.0.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: ["AwaitlessKit"]
)
]
Generates synchronous wrappers for async functions with built-in deprecation controls.
Execute async code blocks synchronously (Swift 6.0+ only).
Automatic runtime thread-safe wrappers for nonisolated(unsafe)
properties.
Direct function for running async code in sync contexts.
import AwaitlessKit
class NetworkManager {
@Awaitless
func downloadFile(url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// Generated automatically without async:
// @available(*, noasync) func downloadFile(url: URL) throws -> Data {
// try Noasync.run({
// try await downloadFile(url: url)
// })
// }
}
// Usage
let data = try NetowrkManager().downloadFile(url: fileURL) // Sync call
class LegacyService {
@Awaitless(.deprecated("Use async version. Sync version will be removed in v2.0"))
func processData() async throws -> String {
try await Task.sleep(nanoseconds: 1_000_000)
return "Processed"
}
}
// Calling sync version shows deprecation warning
let result = try service.processData() // ⚠️ Deprecated warning
class APIClient {
@Awaitless(prefix: "sync_")
func authenticate() async throws -> Token {
try await Task.sleep(nanoseconds: 1_000_000)
}
// Generates:
// @available(*, noasync) func sync_authenticate() throws {
// try Noasync.run({
// try await authenticate()
// })
// }
}
class SharedState: Sendable {
@IsolatedSafe
private nonisolated(unsafe) var _unsafeCounter: Int = 0
// Generates:
//
// internal var counter: Int {
// get {
// accessQueueCounter.sync {
// self._unsafeCounter
// }
// }
//}
@IsolatedSafe(writable: true)
private nonisolated(unsafe) var _unsafeItems: [String] = []
// Generates:
//
// var counter: Int { get }
// internal var items: [String] {
// get {
// accessQueueItems.sync {
// self._unsafeItems
// }
// }
// set {
// accessQueueItems.async(flags: .barrier) {
// self._unsafeItems = newValue
// }
// }
// }
}
class DataManager {
// Autogenetate noasync version alongside new async function
@Awaitless
func loadData() async throws -> [String] {
// New async implementation
}
}
class DataManager {
@Awaitless(.deprecated("Migrate to async version by Q2 2024"))
func loadData() async throws -> [String] {
// Implementation
}
}
class DataManager {
@Awaitless(.unavailable("Sync version removed. Use async version only"))
func loadData() async throws -> [String] {
// Implementation
}
}
class DataManager {
func loadData() async throws -> [String] {
// Pure async implementation
}
}
MIT License. See LICENSE for details.
- Wade Tregaskis for
Task.noasync
from Calling Swift Concurrency async code synchronously in Swift - Zed Editor for its powerful agentic GenAI support
- Anthropic for Claude 3.7 and 4.0 models
Remember: AwaitlessKit is a migration tool, not a permanent solution. Plan your async/await adoption strategy and use this library to smooth the transition.