-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
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
sleep-
스레드를 점유하고, 아예 일정시간동안 해당 스레드를 멈추고 동작시키지 않는 함수 (blocking) →
비효율적 -
메인 스레드에서 사용 시 UI 멈춤
-
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-
async throws: 비동기 함수도 에러를 던질 수 있음 → 호출 시try await -
에러는 필요 시
do-catch로 처리 -
함수가 동기/비동기적이든 결과가 에러가 발생할 수 있다는 것이 더 포괄적인 개념
-
-
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]
}Reactions are currently unavailable