일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 일관성 있는 협력
- iSP
- 다형성
- 상속 조합 폭발적 증가
- 컴파일 타임 의존성
- 책임주도설계
- 유여난 설계
- '기존 설계 재사용
- 런타임 의존성
- 하향식 접근
- OCP
- Swift#flatMap#map#Monad#함수형 프로그래밍#Optional
- 서브 타이핑
- Apple # HIG #iOS15 #iOS14 #Human #Interface #Guidelines #Apple developer # Apple human interface guidelines
- 믹스인
- 합성
- 의존성
- dip
- 알고리즘
- 오브젝트
- 상속
- 유연한 설계
- 추상화
- 설계 재사용
- 행동 호환성
- OOP
- 객체지향
- 명령-쿼리 분리
- 메서드를 통한 해결
- 객체 생성 사용 분리
- Today
- Total
도니의 iOS 프로그래밍 세상
Async let - Swift Concurrency(3) 본문
이번 시간에는 swift concurrency에서 Parallel 실행을 지원해주는 기능인 async let에 대해서 배워보도록 하겠습니다.
결론적으로 말하자면, 해당 기능을 사용함으로써 Serial하게 동작되었던 async 함수들이 Concurrent하게 실행 되도록 합니다.
기존 Async함수 호출시 문제점
우리는 async await 기술을 사용하여, 중첩된 클로저를 사용한 위의 코드를 밑의 코드와 같이 더욱 간결하게 바꿀수 있게 되었습니다.
하지만, 여기서 한가지 문제점이 떠오릅니다.
만약 수백개의 사진을 다운받는 코드라고 가정한다면 위의 코드는 어떻게 작동할까요?
위의 예제와 같이 순서대로 사진을 다운로드 받습니다.
좀더 간단한 용어로 표현하자면 “Serial(동기)”하게 작동되는 형태입니다.
1번부터 3번 이미지가 순서대로 다운로드 되어야 하는 형태이죠.
하지만 우리는 더 빠른 다운로드를 위해 동시에 사진들이 다운로드 받길 원합니다
Async let
동시에 사진을 다운로드 하기 위해서는 async함수 호출시 Task가 생성되어야 합니다.
Task의 정의는 다음과 같습니다.
Task는 비동기성 context를 제공하여 동시에 코드실행을 가능하게 합니다.
하지만, Task는 단순히 async함수를 호출한다고 해서 생성되지 않고, async let이라는 키워드를 통해 생성될 수 있습니다.
결과적으로 async let은 task을 명시적으로 생성해 Parallel하게 동작할수 있도록 하는 가장 간단한 방법입니다.
Async let binding 사용법
func downloadImage() async throws -> UIImage {
// 기존 방식
let image = try await URLSession.shared.data(urlRequest: urlRequest)
let anotherImage = try await URLSession.shared.data(urlRequest: anotherUrlRequest)
DispatchQueue.main.async {
imageView.image = image
anotherImageView.image = anotherImage
}
// async let binding
async let image = URLSession.shared.data(urlRequest: urlRequest)
async let anotherImage = URLSession.shared.data(urlRequest: anotherUrlRequest)
// 실제 이미지가 필요하기 직전까지 다른 코드가 실행
DispatchQueue.main.async {
imageView.image = try await image
anotherImageView.image = try await anotherImage
}
}
기존 async함수 호출시에 반드시 붙여줘야 했던 try await 키워드가 사라지고, 실제로 해당 image가 쓰일때 try await 키워드를 붙여주게 됩니다.
이를 통해 우리는 serial하게 이미지를 다운로드 하지 않고 Concurrent하게 이미지 다운로드가 가능해집니다.
이에대한 원리를 알아보도록 하겠습니다.
Async let의 원리
기존 async 함수 동작방식
기존 async함수를 호출할 경우 방식은 다음과 같습니다.
기존 async함수는 하나의 URLSession을 담당하고, 데이터 다운로드가 끝났을때 result라는 변수에 할당합니다.
그리고 나서 다음 statements가 실행되는 방식입니다.
이는 결국 해당 URLSession이 결과를 호출한 뒤에, 다음 statement가 실행되기에 Serial하게 동작합니다.
우리는 이렇게 데이터 다운로드가 끝났을때까지 기다리는게 아닌, 데이터가 쓰이기 전까진 다른 일들을 수행하고 싶습니다.(요리를 한다고 가정합시다. 우리는 밥을 짓는동안 국을 끓일수도 있고, 반찬을 만들수도 있습니다. 밥이 필요한 순간은 밥을 먹기 직전입니다. 그러니 우리는 실제 밥을 먹기전까진 밥이 필요하지 않기에 다른 일들을 수행할 수 있습니다.)
Async let의 동작방식
앞의 상황과 다르게 async let을 붙임으로써 Swift는 child task를 만들어줍니다.
흰색 화살표는 parent task의 arrow이고, 초록색은 child task의 arrow입니다.
두개의 실행흐름이 생겨, child task에서는 이미지 다운로드를 진행하고 parent task에서는 다음 statement를 실행하게 됩니다.
계속해서 다음 statement를 실행하다가 “앞선 child task에서 다운로드 되는 데이터”가 진정으로 사용되는 곳을 만나게 됐을때 parent task는 child task의 완료를 기다리게 되는 것입니다.
URLSession은 throw함수이기 때문에 만약 에러가 발생했을 경우, 해당 에러가 결국 propagate되는 것또한 해당 데이터가 실제 쓰이는 곳에서 전파되게 됩니다.
이는 결국 Task tree형태로 이해하면 됩니다.
Asnyc let의 사용유무에 따른 차이
결과적으로 async let binding을 사용함으로써 이미지 다운로드를 parallel하는게 가능해졌습니다.
위 사항을 적용한 예제코드는 다음과 같습니다.
https://github.com/yudonlee/AsyncLetBinding
해당 예제에서도 확인할 수 있듯이, 세가지 이미지를 동시에 다운로드 받는것과 순차적으로 받는것은 속도차이에서 압도적인 차이가 발생합니다.
async let을 통해서 이미지 다운로드하는 도중 실패할 경우 swift에서는 실패한 task외에 다른 task들에 cancellation을 표시해줍니다. 그리고 structured task가 직간접적으로 종료되고 나서 함수는 error을 던지며 종료됩니다.
이는 structured concurreny의 근본이며, 실수로 task leak이 되는 현상을 방지하게 됩니다.
결론
- async 함수의 단순 호출은 child task를 생성하지 않는다
- 그리하여 serial하게 동작하기 때문에 병목현상이 발생할 수 있다.
- 이를 가장 간단하게 해결하는 해결책이 async let이다.
- 이는 child task를 생성해 parallel하게 코드 실행을 가능하게 해줍니다.
- async let을 사용하면 실제 데이터가 사용되기 직전까지 함수는 기다리지 않고 계속해서 실행이 가능합니다.
다음시간에는 for문 내부에서 동작하며 data race를 방지할 수 있도록 고안된 group task를 살펴보도록 하겠습니다.
참조
https://developer.apple.com/videos/play/wwdc2021/10134/?time=678
'Swift' 카테고리의 다른 글
[Swift] flatMap, map의 차이 - Monad로의 여정(1) (0) | 2023.12.15 |
---|---|
Group Task - Swift Concurrency(4) (0) | 2023.03.20 |
Swift Error handling(try, catch) - Swift Concurrency(2) (0) | 2023.02.09 |
Swift Concurrency(1) - 기본개념 및 작동원리 (0) | 2023.02.08 |
[Swift] ARC weak self를 하는 이유(delayed deinitialization, memory leak) - ARC 응용편 5탄 (0) | 2023.01.15 |