Xcode's refactoring options for async/await
Automatically adopt async functions in your codebase with ease

Xcode may offer up to three refactoring options when right-clicking on a completion handler-based function:
- Convert Function to Async
- Add Async Alternative
- Add Async Wrapper
I will explain the result of each refactoring option based on the example given below.
func fetchRandomPictures(count: Int = 20, completion: @escaping (Result<[Picture], Error>) -> Void) {
let url = pictureOfTheDayURL(additionalParameters: [
"count": "\(count)"
])
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data {
let decoder = JSONDecoder()
do {
let pictures = try decoder.decode([Picture].self, from: data)
completion(.success(pictures))
} catch {
completion(.failure(error))
}
} else if let error = error {
completion(.failure(error))
} else {
completion(.failure("unknownFailure"))
}
}
task.resume()
}
Videos for each refactoring option are available in my related Twitter thread.
Convert Function to Async
The existing function gets replaced. This is a breaking change, i.e the function signature gets changed and you need to update all places that call the existing function!
func fetchRandomPictures(count: Int = 20) async throws -> [Picture] {
let url = pictureOfTheDayURL(additionalParameters: [
"count": "\(count)"
])
let request = URLRequest(url: url)
return try await withCheckedThrowingContinuation { continuation in
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data {
let decoder = JSONDecoder()
do {
let pictures = try decoder.decode([Picture].self, from: data)
continuation.resume(with: .success(pictures))
} catch {
continuation.resume(with: .failure(error))
}
} else if let error = error {
continuation.resume(with: .failure(error))
} else {
continuation.resume(with: .failure("unknownFailure"))
}
}
task.resume()
}
}
Add Async Alternative
A new async function is added. The existing function signature stays as-is, but the function implementation was replaced to use the newly created async function.
@available(*, renamed: "fetchRandomPictures(count:)")
func fetchRandomPictures(count: Int = 20, completion: @escaping (Result<[Picture], Error>) -> Void) {
Task {
do {
let result = try await fetchRandomPictures(count: count)
completion(.success(result))
} catch {
completion(.failure(error))
}
}
}
func fetchRandomPictures(count: Int = 20) async throws -> [Picture] {
let url = pictureOfTheDayURL(additionalParameters: [
"count": "\(count)"
])
let request = URLRequest(url: url)
return try await withCheckedThrowingContinuation { continuation in
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data {
let decoder = JSONDecoder()
do {
let pictures = try decoder.decode([Picture].self, from: data)
continuation.resume(with: .success(pictures))
} catch {
continuation.resume(with: .failure(error))
}
} else if let error = error {
continuation.resume(with: .failure(error))
} else {
continuation.resume(with: .failure("unknownFailure"))
}
}
task.resume()
}
}
Add Async Wrapper
The existing function is unchanged, and a new async function is added that uses withCheckedThrowingContinuation to reuse the existing function.
@available(*, renamed: "fetchRandomPictures(count:)")
func fetchRandomPictures(count: Int = 20, completion: @escaping (Result<[Picture], Error>) -> Void) {
let url = pictureOfTheDayURL(additionalParameters: [
"count": "\(count)"
])
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data {
let decoder = JSONDecoder()
do {
let pictures = try decoder.decode([Picture].self, from: data)
completion(.success(pictures))
} catch {
completion(.failure(error))
}
} else if let error = error {
completion(.failure(error))
} else {
completion(.failure("unknownFailure"))
}
}
task.resume()
}
func fetchRandomPictures(count: Int = 20) async throws -> [Picture] {
return try await withCheckedThrowingContinuation { continuation in
fetchRandomPictures(count: count) { result in
continuation.resume(with: result)
}
}
}
Conclusion
The least disruptive refactoring option is Add Async Wrapper which I recommend.
If you have full control over the call sites and don't need to provide compatibility, go with the most disruptive refactoring option Convert Function to Async.
About the author
I am a Software Engineer working on open source and enterprise mobile SDKs for iOS and MacOS developers written in Swift. From 馃嚛馃嚜 and happily living in 馃嚭馃嚫
Comments
Join the discussionNo comments yet. Be the first to comment.



