티스토리 뷰
비동기 처리, 왜 어려울까?
iOS 개발을 하다 보면 네트워크 요청, 파일 읽기, 데이터베이스 작업처럼 시간이 걸리는 작업을 자주 만나게 된다. 이런 작업을 비동기로 처리해야 하는데, 기존의 Completion Handler 방식은 코드가 길어지고 가독성이 떨어진다는 단점이 있다.
예를 들어, URLSession을 이용해 데이터를 가져올 때 기존에는 이렇게 작성했다.
func fetchData(completion: @escaping (Result<data, error="">) -> Void) {
let url = URL(string: "https://api.example.com/data")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "No Data", code: 0, userInfo: nil)))
return
}
completion(.success(data))
}
task.resume()
}
😵💫 너무 복잡하다!
콜백(Completion Handler)이 중첩되면 코드가 점점 깊어지면서 유지보수가 힘들어진다.
Async/Await의 등장
Swift 5.5부터 async/await가 도입되면서 비동기 코드가 동기 코드처럼 읽기 쉬워졌다.
위의 예제를 async/await를 사용해 개선해보자.
func fetchData() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
가독성이 훨씬 좋아졌다!
- await 키워드 덕분에 동기 코드처럼 이해하기 쉬워졌다.
- throws를 사용해 에러 처리를 더 자연스럽게 할 수 있다.
- completion을 사용할 필요가 없어서 중첩이 사라졌다.
Async/Await의 기본 개념
1. async 키워드
함수가 비동기적으로 실행된다는 것을 표시한다. async가 붙은 함수는 await을 사용해 호출해야 한다.
2. await 키워드
비동기 작업이 완료될 때까지 기다린다. await을 사용하면 Swift가 자동으로 작업을 일시 중단했다가 완료되면 다시 실행한다.
3. throws와 함께 사용 가능
비동기 함수에서도 throws를 사용해 에러를 던질 수 있다. 이때, try await을 함께 사용한다.
func fetchData() async throws -> Data
위 함수는 데이터를 가져오는 동안 오류가 발생할 수 있으므로 throws를 포함하고 있다. 따라서 호출할 때는 다음과 같이 try await을 사용해야 한다.
do {
let data = try await fetchData()
print("Data received: \(data)")
} catch {
print("Error: \(error)")
}
실무에서 Async/Await 활용하기
✅ 1. DispatchQueue와 함께 사용
기존에는 DispatchQueue.global().async를 사용해 백그라운드에서 실행해야 했지만, async/await에서는 Task를 사용한다.
Task {
let data = try await fetchData()
print("Data: \(data)")
}
- Task {}를 사용하면 비동기 컨텍스트에서 함수를 호출할 수 있다.
- Task는 기본적으로 메인 스레드에서 실행되지만, 내부적으로 await을 만나면 적절한 스레드에서 실행된다.
✅ 2. 여러 개의 비동기 작업 동시에 실행하기 (async let)
동시에 실행할 수 있는 비동기 작업이 있다면 async let을 사용해 성능을 향상시킬 수 있다.
async let firstData = fetchData()
async let secondData = fetchData()
let result1 = try await firstData
let result2 = try await secondData
- async let을 사용하면 두 개의 네트워크 요청이 동시에 실행된다.
- await을 사용하면 모든 작업이 끝날 때까지 기다린다.
✅ 3. TaskGroup을 활용한 병렬 처리
여러 개의 비동기 작업을 그룹으로 묶어 효율적으로 실행할 수도 있다.
func fetchMultipleData() async throws -> [Data] {
return try await withThrowingTaskGroup(of: Data.self) { group in
let urls = [
URL(string: "https://api.example.com/data1")!,
URL(string: "https://api.example.com/data2")!
]
for url in urls {
group.addTask {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
var results: [Data] = []
for try await data in group {
results.append(data)
}
return results
}
}
- withThrowingTaskGroup을 사용하면 여러 개의 비동기 작업을 동시에 실행할 수 있다.
- 작업이 완료될 때까지 for try await을 사용해 기다린다.
정리
✅ async/await을 사용하면 비동기 코드가 동기 코드처럼 읽기 쉬워진다.
✅ 기존 completion handler 방식보다 가독성이 뛰어나고, 중첩 문제를 해결할 수 있다.
✅ async let, TaskGroup을 활용하면 여러 개의 비동기 작업을 효과적으로 처리할 수 있다.
이제 실무에서도 적극적으로 async/await을 활용해 깔끔하고 유지보수하기 쉬운 코드를 작성해보자!
'iOS' 카테고리의 다른 글
| iOS 앱 생명주기(App Lifecycle) 제대로 알아보기 (0) | 2025.03.03 |
|---|---|
| OperationQueue - iOS에서 비동기 작업을 효율적으로 관리하는 방법! (0) | 2025.02.27 |
| iOS에서 비동기 작업을 처리하려면? GCD(DispatchQueue)를 이해하자! (0) | 2025.02.25 |
| Run Loop – iOS 앱은 어떻게 끊기지 않고 동작할까? (0) | 2025.02.24 |
| 캡처 리스트(Capture List) – 클로저가 변수를 기억하는 방법 (0) | 2025.02.21 |