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 webviewLogin, which initiates a web-based login flow 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 webviewLogin and refreshAccessToken methods are declared as:

func webviewLogin(_ request: WebviewLoginRequest) async throws -> AuthToken (1)
func refreshAccessToken(authToken: AuthToken) async throws -> AuthToken (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 webviewLogin

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

  • BrightFutures

  • Swift Concurrency

AppDelegate.reachfive().webviewLogin(WebviewLoginRequest(
    state: "zf3ifjfmdkj",
    nonce: "n-0S6_PzA3Ze",
    scope: ["openid", "profile", "email"],
    presentationContextProvider: self
    ))
    .onSuccess { authToken in (1)
        // Use the profile's authentication token
        goToProfile(authToken)
    }
    .onFailure { error in (1)
    // Return a ReachFive error
    }
1 This uses .onSuccess to handle the AuthToken and .onFailure to manage errors.
do { (1)
    let authToken = try await AppDelegate.reachfive().webviewLogin(
        WebviewLoginRequest(
            state: "zf3ifjfmdkj",
            nonce: "n-0S6_PzA3Ze",
            scope: ["openid", "profile", "email"],
            presentationContextProvider: self
        )
    )
    // Handle the successful authentication token (like .onSuccess)
    Task { @MainActor in (2)
        goToProfile(authToken)
    }
} catch { (3)
// Handle the error (like .onFailure)
}
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 async throws method.

Thread Management for UI Operations

When using Swift concurrency, asynchronous operations like webviewLogin 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 created with @MainActor to ensure thread safety, as UI updates in iOS must occur on the main thread.

Unlike BrightFutures, where thread management was often abstracted by the library, Swift concurrency 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.