이번엔 Swift Concurrency
문법을 사용한 코드로 네트워크 통신을 해볼 것이다. Swift Concurrency 문법이 궁금하다면 해당 내용을 정리한 포스팅을 참고하면 된다.
동시성 코드에서는 일 처리에 대한 결과를 기다릴 때 await
를 사용하고, 에러를 발생시킬 때 throw
키워드를 사용한다.
func fetchTodos() async throws -> [Todo] {
let urlString = "https://jsonplaceholder.typicode.com/todos"
// 1. URL 유효성 체크
guard let url = URL(string: urlString) else {
throw CustomError.invalidURL
}
// 2. 네트워크 요청 : async 메소드인 data 사용
let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
// 3. esponse를 HTTPURLResponse로 타입 캐스팅
guard let response = response as? HTTPURLResponse else {
throw CustomError.invalidResponse
}
// 4. 응답 코드 체크 (200번대여야 통신 성공)
guard (200..<300).contains(response.statusCode) else {
throw CustomError.badResponse(response.statusCode)
}
// 5. 디코딩
do {
let decodedData = try JSONDecoder().decode([Todo].self, from: data)
return decodedData
} catch {
throw CustomError.decodingFailed
}
}
UI
와 데이터를 바인딩하는 작업은 메인 스레드에서 이루어져야 하는데, 이 작업에 대해 @MainActor
를 선언해주면 해당 메소드가 메인 스레드에서 실행된다. 또, async
함수를 실행할 경우 Task
클로저를 통해 호출해야 한다.
class TodoViewController: UIViewController {
override func viewDidLoad() {
// ... //
Task {
await bind()
}
}
// ... //
@MainActor
private func bind() async {
do {
let todos = try await viewModel.fetchTodos()
todoTableView.reloadData(todos: todos)
} catch let error as CustomError { // 직접 정의한 에러가 발생했을 경우 처리
print(error.debugMessage)
} catch let error { // 커스텀 에러 이외의 상황에서의 에러 처리
print(CustomError.etc(error).debugMessage)
}
}
}
completionHandler
에서 에러 상황을 구체적으로 처리한 것과 달리 async
함수는 코드가 굉장히 짧아졌는데, 같은 에러 상황에서 디버그 메시지가 어떻게 출력되는지 보고 싶어서 테스트를 진행하였다. 맥미니 와이파이를 끄고 실행해보았다.
bind()
함수에서 CustomError.etc
로 처리한 디버그 메시지가 출력된 것을 확인했다.
urlString
의 맨 뒤 글자를 지운 뒤 실행해보았다.
fetchTodos()
에서 throw
한 badResponse
의 디버그 메시지가 출력되었다.