# Xcode's refactoring options for async/await

Xcode may offer up to three refactoring options when right-clicking on a completion handler-based function:

1. Convert Function to Async
2. Add Async Alternative
3. Add Async Wrapper

I will explain the result of each refactoring option based on the example given below.

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

%[https://twitter.com/MarcoEidinger/status/1564782392253984768]

# 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!

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

```swift
    @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.

```Swift
    @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`.
