AwaitlessKit

6.0.0

Simplifies the migration to async/await. It likely performs better than your ad hoc hacks.
bonkey/AwaitlessKit

What's New

6.0.0

2025-05-29T05:02:12Z
  • Macros Got Smarter! 🧠 Both @Awaitless and #awaitless can now juggle all sorts of function arguments way better. Unlabeled params? inout? Default values? Generics? Bring 'em on! Things should "just work" more often.
  • Test Overload! 🧪 Added a mountain of new tests to cover all these fancy argument types and make sure the macros are behaving. More tests = more peace of mind!

Full Changelog: 5.0.0...6.0.0

Ask DeepWiki

Tests on Xcode 16 Build on Xcode 15 Tests on Linux

AwaitlessKit

AwaitlessKit is a collection of Swift macros and utilities that lets you commit unspeakable asynchronous sins - like calling async functions from a nonasync context without awaiting them properly.

In other words, it simplifies the migration to async/await code from Structured Concurrency using some loopholes. It likely performs better than your ad hoc hacks.

Remember! This framework deliberately sidesteps type-safety guardrails. Though it leverages battle-tested patterns, do your due diligence on edge cases before using it in production.

Motivation

From Swift's Evolution Improving the approachability of data-race safety:

Introducing async/await into an existing codebase is difficult to do incrementally, because the language does not provide tools to bridge between synchronous and asynchronous code. Sometimes programmers can kick off a new unstructured task to perform the async work, and other times that is not suitable, e.g. because the synchronous code needs a result from the async operation. It’s also not always possible to propagate async throughout callers, because the function signature might be declared in a library dependency that you don’t own.

AwaitlessKit aims to bridge that gap and simplify the adoption of async/await.

Requirements

While AwaitlessKit should work with Xcode 15 and Swift 5.x, it's less tested, and support is considered experimental.

For the best experience, Xcode 16 with Swift 6.0 compiler is highly recommended. Your project can still be in Swift 5.x.

Available macros

#awaitless()

Note: not yet available in Swift 5.x / Xcode 15

A freestanding expression macro that executes async code blocks synchronously.

Particularly valuable when interfacing with third-party APIs or legacy systems where asynchronous context isn't available, but you need to integrate with your async implementations.

@Awaitless

An attached macro that automatically generates synchronous counterparts for your async functions.

Ideal for API design patterns requiring both synchronous and asynchronous interfaces, eliminating the need to manually maintain duplicate implementations and providing simple deprecation for the future.

@IsolatedSafe

An attached property macro that implements a serial dispatch queue to provide thread-safe access to nonisolated(unsafe) properties.

Offers runtime concurrency protection when compile-time isolation isn't feasible, effectively preventing data races through a property protected with DispatchQueue.

Available functions

Noasync.run()

Allows to run async code in noasync context.

Powers @Awaitless() and #awaitless() macros.

More details in Calling Swift Concurrency async code synchronously in Swift

Usage

Awaitless

import AwaitlessKit

final class AwaitlessExample: Sendable {

    // Basic usage - generates a sync version with same name
    @Awaitless
    func fetchData() async throws -> Data {
        // ...async implementation
    }

    func onlyAsyncFetchData() async throws -> Data {
        // ...async implementation
    }

    // With deprecation warning
    @Awaitless(.deprecated("Synchronous API will be phased out, migrate to async version"))
    func processItems() async throws -> [String] {
        // ...async implementation
    }

    // Make sync version unavailable
    @Awaitless(.unavailable("Synchronous API has been removed, use async version"))
    func loadResources() async throws -> [Resource] {
        // ...async implementation
    }

    // Custom prefix for generated function
    @Awaitless(prefix: "sync_")
    func loadConfig() async -> Config {
        // ...async implementation
    }

    public func run() {
        // Call generated sync versions
        let data = try fetchData()
        let items = try processItems() // Shows deprecation warning
        let config = sync_loadConfig()

        // Or use the freestanding macro
        let result = try #awaitless(try onlyAsyncFetchData())
    }
}

IsolatedSafe

final class IsolatedSafeExample: Sendable {
    // (1a) Thread-safe wrapper for unsafe property access
    @IsolatedSafe
    private nonisolated(unsafe) var _unsafeStrings: [String] = ["Hello", "World"]

    // (2a) Thread-safe wrapper with write access
    @IsolatedSafe(writable: true)
    private nonisolated(unsafe) var _unsafeProcessCount: Int = 0

    public func run() {
        // (1b) Safe access to "_unsafeStrings" through generated thread-safe "string" property
        strings.append("and")
        strings.append("universe")

        // (2b) Safe access to "_unsafeProcessCount" through generated thread-safe "processCount" property
        processCount += 1
    }
}

Installation

Add AwaitlessKit to your Package.swift:

dependencies: [
    .package(url: "https://github.com/yourusername/AwaitlessKit.git", from: "5.0.0")
],
targets: [
    .target(
        name: "YourTarget",
        dependencies: ["AwaitlessKit"]
    )
]

SampleApp

The SampleApp included in the repo is just a simple demo app showing these features in action.

Credits

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Mon Jun 16 2025 06:28:11 GMT-0900 (Hawaii-Aleutian Daylight Time)