I have something like this in my code and I was wondering if it is possible to make the compiler happy with the closures that I have:
struct Configuration {
var acceptAction: () -> Void
var denyAction: () -> Void
}
func someExample() async throws -> String {
doSomething(
configuration: Configuration(
acceptAction: {
// call async method fetchSomething that returns a string
}, denyAction: {
// throw
}
)
)
}
func doSomething(configuration: Configuration) {
// one of the closures will be called here
}
func fetchSomething() async throws -> String {
//
}
Any ideas are welcome.
CodePudding user response:
Before we get to your example, let us first consider a simpler example. If you are trying to wrap a closure-based completion handler in an async
method, you would use withCheckedThrowingContinuation
. E.g.
func bar(completion: @escaping (Result<String, Error>) -> Void) {
// ...
}
You could call that using withCheckedThrowingContinuation
:
func baz() async throws -> String {
try await withCheckedThrowingContinuation { continuation in
bar { result in
switch result {
case .failure(let error): continuation.resume(throwing: error)
case .success(let string): continuation.resume(returning: string)
}
}
}
}
So, using your example, it might be:
func someExample() async throws -> String {
try await withCheckedThrowingContinuation { continuation in
doSomething(
configuration: Configuration {
Task {
do {
let string = try await self.fetchSomething()
continuation.resume(returning: string)
} catch {
continuation.resume(throwing: error)
}
}
} denyAction: {
continuation.resume(throwing: ExampleError.denied)
}
)
}
}
Generally, I would expect the denyAction
to pass the error as a parameter (and I would use that), but in this case, you didn't supply any specific error explaining why it was denied, so I just made one:
enum ExampleError: Error {
case denied
}
As an aside, Xcode 13.4 has refactoring tools to simplify the process a little. For example, consider a traditional Result
-based completion handler closure. Xcode can recognize automatically that it has an error type, and will generate the template with withCheckedThrowingContinuation
:
See WWDC 2021 video Swift concurrency: Update a sample app for examples of how to wrap methods with completion handler closures into Swift concurrency. It shows the use of these refactoring tools, too.
CodePudding user response:
You could do something like this. This is a way to start an unstructured task.
func someExample() async throws -> String {
doSomething(
configuration: Configuration(
acceptAction: {
// call async method fetchSomething that returns a string
let _ = Task {
try await fetchSomething()
}
}, denyAction: {
// throw
}
)
)
}
Here is another example:
func fetchData(completion: @MainActor @escaping (Result<[UIImage], Error>) -> Void) {
URLSession.shared.dataTask(with: URL(string: "..some URL")!) { data, response, error in
// .. Decode data to a result
let result: Result<[UIImage], Error> = .success([])
Task {
await completion(result)
}
}
}