async / await
async: 비동기 함수, 호출 시 await 필요
async throws: 에러 가능성 있는 비동기 함수, 호출 시 try await 필요
| 구분 |
async |
async throws |
| 에러 가능성 |
없음 |
있음 (throw) |
| 호출 시 |
await |
try await |
| 에러 처리 |
필요 없음 |
do-catch / try? / try! |
동기 함수에서는 직접 await 사용 불가 -> Task {} 안에서 호출해야 함
기존 방식 vs Concurrency 방식
기존 비동기 함수 정의 방법
- 오래 걸려서 얻는 결과값을 반드시 콜백 클로저로만 결과 전달 가능
func getImage(callback: @escaping (UIImage?) -> Void) {
DispatchQueue.global().async {
sleep(5)
callback(UIImage(systemName: "star"))
}
}
Concurrency 비동기 함수 정의 방법
func getImage() async -> UIImage? {
try? await Task.sleep(for: .seconds(2))
return UIImage(systemName: "star")
}
Task {
let image = await getImage()
}
sleep vs Task.sleep
func blockingSleep() {
print("blocking 시작")
// 2초 동안 스레드를 완전히 점유 -> 메인 스레드가 멈춰서 그동안 아무 것도 못 함
sleep(2)
print("blocking 끝")
}
blocking 시작
(2초 멈춤)
blocking 끝
Task.sleep
-
비동기 함수, Task만 일시 중단 후 재개 (non-blocking) → 효율적
-
시간이 종료되기 전에 작업이 취소되면, CancellationError를 던짐
func nonBlockingSleep() {
Task {
print("non-blocking 시작")
// 스레드 점유하지 않고 다른 Task코드가 계속 실행됨
try await Task.sleep(for: .seconds(2))
print("non-blocking 끝")
}
}
non-blocking 시작
(2초 후 재개)
non-blocking 끝
GCD vs Swift Concurrency
GCD 비동기
- 다른 스레드로 작업을 던짐 → 끝날 때 콜백으로 결과 전달
- 흐름 제어가 콜백 클로저에 의존
func method(closure: @escaping (String) -> Void) {
closure("결과")
}
method { result in
print(result)
}
Concurrency 비동기 함수 (async/await)
- 비동기 함수 자체가 값 반환 가능 → 콜백 불필요
- 실행 중간에 멈췄다가 재개 가능 (non-blocking)
func asyncMethod() async -> String {
let result = await otherMethod()
return result
}
Task {
let result = await asyncMethod()
print(result)
}
- 명시적으로
@MainActor 지정하지 않으면 → 런타임이 적절한 실행자(executor) 선택 (보통 백그라운드)
@MainActor
func updateUI() {
label.text = "업데이트 완료"
}
// Task 생성 시 메인 액터 지정
Task { @MainActor in
// 이 블록 안 코드는 메인 스레드에서 실행
print("main:", Thread.isMainThread) // true
}
// 특정 구문만 메인 액터로 hop (실행자(executor) 전환)
// - `async` 함수는 실행 중간에 다른 실행자(executor) 로 넘어갈 수 있음
// - `DispatchQueue.main.async {}`랑 비슷한 개념이지만, Swift Concurrency는 컴파일러가 보장
Task {
let data = await fetchData() // 백그라운드에서 실행될 수 있음
await MainActor.run {
// 여기서만 메인 스레드 보장
imageView.image = UIImage(data: data)
}
}
에러 처리
-
async throws
-
Task도 에러를 담을 수 있음
-
Task 안에서는 do-catch로 에러처리를 하지 않아도, Task 내부로 에러를 던질 수 있음
-
Task<Success, Failure: Error>: Task가 에러를 가지고 있음
-
필요한 경우 Task 안에서 에러 처리
Task.yield() 명시적인 중단 지점 만들기
- 협력적 스케줄링(cooperative scheduling)을 도와주는 용도 -> 긴 루프 안에서 적절히 써주면 좋음
- 중단 지점이 없는 긴 실행 작업의 중간에서 자발적으로 자신을 중단시켜,
- 다른 작업이 잠시 실행될 수 있도록 한 후에, 이 작업으로 실행이 돌아오게 할 수 있음
func getImagesArray() async -> [UIImage?] {
let image1 = await getImage()
// 다른 작업에 CPU 양보
await Task.yield()
let image2 = await getImage()
let image3 = await getImage()
return [image1, image2, image3]
}
async / await
async: 비동기 함수, 호출 시await필요async throws: 에러 가능성 있는 비동기 함수, 호출 시try await필요기존 방식 vs Concurrency 방식
기존 비동기 함수 정의 방법
Concurrency 비동기 함수 정의 방법
sleep vs Task.sleep
sleep스레드를 점유하고, 아예 일정시간동안 해당 스레드를 멈추고 동작시키지 않는 함수 (blocking) →
비효율적메인 스레드에서 사용 시 UI 멈춤
Task.sleep비동기 함수, Task만 일시 중단 후 재개 (non-blocking) →
효율적시간이 종료되기 전에 작업이 취소되면,
CancellationError를 던짐GCD vs Swift Concurrency
GCD 비동기
Concurrency 비동기 함수 (async/await)
@MainActor지정하지 않으면 → 런타임이 적절한 실행자(executor) 선택 (보통 백그라운드)에러 처리
async throwsasync throws: 비동기 함수도 에러를 던질 수 있음 → 호출 시try await에러는 필요 시
do-catch로 처리함수가 동기/비동기적이든 결과가 에러가 발생할 수 있다는 것이 더 포괄적인 개념
Task도 에러를 담을 수 있음Task안에서는do-catch로 에러처리를 하지 않아도,Task내부로 에러를 던질 수 있음Task<Success, Failure: Error>:Task가 에러를 가지고 있음필요한 경우 Task 안에서 에러 처리