Migrating from BrightFutures to Swift Concurrency

BrightFutures is a Swift library for managing asynchronous operations using futures. A future represents a value that may become available later, suitable for tasks like authentication in the ReachFive iOS SDK. The .onSuccess and .onFailure methods are one of the main ways to handle successful results and errors, respectively.

Why is BrightFutures being discontinued?

BrightFutures development stopped due to Swift’s native concurrency features (see Swift docs: Concurrency), which offer a standardized alternative for asynchronous operations.

BrightFutures was used for asynchronous operations like refreshAccessToken, which uses exchange a refresh token and returns an authentication token or a ReachFive error.

The Reach5Future SDK will be released to maintain compatibility with BrightFutures' Future for existing users. The main ReachFive iOS SDK will adopt async/await for future development.

Using async throws for Asynchronous Operations

Swift’s native concurrency introduces async throws to declare functions that are both asynchronous and capable of throwing errors. For example, the ReachFive SDK’s refreshAccessToken and registerNewPasskey methods are declared as:

func refreshAccessToken(authToken: AuthToken) async throws -> AuthToken (1)
func registerNewPasskey(withRequest request: NewPasskeyRequest, authToken: AuthToken) async throws (2)
1 The async keyword indicates the function performs asynchronous work (e.g., network requests), requiring await when called.
2 The throws keyword means it may throw an error, usually a ReachFiveError, requiring try within a do/catch block.

This replaces BrightFutures' Future<AuthToken, ReachFiveError>, where .onSuccess handles the AuthToken and .onFailure handles errors.

Replacing result handling of Future with do/catch and try await

Swift’s native concurrency uses asynchronous methods, with do-catch statements to handle errors, replacing BrightFutures. The do block processes successful results (like .onSuccess), and the catch block handles errors (like .onFailure). When calling an async throws function, try await is used within a do clause to capture the result or throw an error to the catch clause.

Example: Migrating refreshAccessToken

Below is the refreshAccessToken method in BrightFutures and its Swift concurrency equivalent, using async throws and do-catch.

  • BrightFutures

  • Swift Concurrency

AppDelegate.reachfive()
    .refreshAccessToken(authToken: staleToken)
    .onSuccess { freshToken in (1)
        // Use the profile's authentication token
        goToProfile(freshToken)
    }
    .onFailure { error in (1)
        // The error has type ReachFiveError
        print(error.message())
        AppDelegate.storage.removeToken()
    }
1 This uses .onSuccess to handle the AuthToken and .onFailure to manage errors.
do { (1)
    let freshToken = try await AppDelegate.reachfive().refreshAccessToken(authToken: staleToken)
    Task { @MainActor in (2)
        goToProfile(freshToken)
    }
} catch { (3)
    // The error has type Error
    print(error.localizedDescription) (4)
    AppDelegate.storage.removeToken()
}
1 The do block uses try await to call async throws methods, handling the successful AuthToken and navigation to the profile page.
2 Task { @MainActor in …​ } creates an asynchronous context on the main thread, ensuring UI updates are safe. This is required when calling async methods from a synchronous context, such as a UIKit or AppKit event handler, and when you need to update UI elements.
3 The catch block manages errors from any throws method.
4 Migrate usage of ReachFiveError.message() to Error.localizedDescription, or cast the error

Thread Management for UI Operations

When using Swift concurrency, asynchronous operations like refreshAccessToken must be wrapped in a Task when called from a synchronous context, such as a UIKit or AppKit event handler. For operations that update UI elements (e.g., displaying the profile in goToProfile), the Task should be annotated with @MainActor to ensure thread safety, as UI updates in iOS must occur on the main thread.

Unlike BrightFutures, which use a default threading behaviour where methods that started from the main thread would execute all their asynchronous operations also in the main thread, Swift concurrency uses background threads a lot more. This requires integrators to be more explicit about threading. The example above uses Task { @MainActor in …​ } to ensure goToProfile occur on the main thread, aligning with the async throws pattern.

Key Differences and Benefits

  • Syntax: do/catch with async/await is native, reducing reliance on BrightFutures.

  • Structured Concurrency: Swift simplifies task lifecycles and cancellation compared to BrightFutures' closures.

  • Error Handling: catch supports specific error types (e.g., catch ReachFiveError.AuthFailure { …​ }) for precise control.

  • Thread Management: Swift concurrency requires explicit handling of threads for UI operations, offering more control but demanding careful implementation.

  • Integration: Using this approach, you have better integration with Swift’s native concurrency model, making your code more idiomatic and maintainable.