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