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.
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
.
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
withasync
/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.