일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- struct
- 비동기
- 상호배제
- decode
- 데드락
- 앨런
- 동시성
- 인프런
- forEach
- async
- scrollview
- Algorithm
- @state
- SwiftUI
- core data
- IOS
- 오브젝트
- Apple Developer Academy
- UserDefaults
- 가상 메모리
- 알고리즘
- deadlock
- Swift
- 100 days of SwiftUI
- 운영체제
- 동기화
- Linked List
- COLOR
- Codable
- 프로세스 스케줄링
Archives
- Today
- Total
기어가더라도 제대로
[iOS-WWDC] "Meet async/await in Swift" 요약 정리 본문
1. 비동기 처리를 위한 Completion Handler
- 비동기 처리를 위해서 Completion handler를 많이 사용
- 문제점
- 비동기 작업이 실패하더라도 이 함수의 호출자(Caller)는 결과를 마냥 기다림
- guard else { return } 을 사용하면 completion 에 담지 않아도 되는 경우가 생김
- 에러 핸들링을 강제할 수가 없다는 게 문제
func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) { let request = thumbnailURLRequest(for: id) let task = URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { completion(nil, error) } else if (response as? HTTPURLResponse)?.statusCode != 200 { completion(nil, FetchError.badID) } else { guard let image = UIImage(data: data!) else { completion(nil, FetchError.badImage) return } image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in guard let thumbnail = thumbnail else { completion(nil, FetchError.badImage) return } completion(thumbnail, nil) } } } task.resume()
- 이를 해결하기 위해서 Result<value, error> 를 만들었음
- 그러나 여전히 들여쓰기나 에러 핸들링 때문에 코드를 이해하기 어려움
- 비동기 작업이 실패하더라도 이 함수의 호출자(Caller)는 결과를 마냥 기다림
func fetchThumbnail(for id: String, completion: @escaping (Result<UIImage, Error>) -> Void) {
let request = thumbnailURLRequest(for: id)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
} else if (response as? HTTPURLResponse)?.statusCode != 200 {
completion(.failure(FetchError.badID))
} else {
guard let image = UIImage(data: data!) else {
completion(.failure(FetchError.badImage))
return
}
image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
guard let thumbnail = thumbnail else {
completion(.failure(FetchError.badImage))
return
}
completion(.success(thumbnail))
}
}
}
task.resume()
}
2. async / await 대작전
- 이런 고민을 말끔하게 씻어줄 녀석이
async/await
- 들여쓰기 문제를 해결하기 위해 비동기 코드가 아니라 일반 코드처럼 선언할 수 있게 도와주는 키워드
- 들여쓰기 문제가 해결
- 코드 이해가 수월(위에 코드가 6줄로 끝남)
- 읽기 전용 프로퍼티에도
async/await
를 붙임
func fetchThumbnail(for id: String) async throws -> UIImage {
let request = thumbnailURLRequest(for: id)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
let maybeImage = UIImage(data: data)
guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
return thumbnail
}
extension UIImage {
var thumbnail: UIImage? {
get async {
let size = CGSize(width: 40, height: 40)
return await self.byPreparingThumbnail(ofSize: size)
}
}
}
3. 일반 함수와 비동기 함수(async func)의 차이
- Thread를 제어권을 언제 넘길 수 있느냐 가 리빙 포인트
일반 함수
return
이 호출되어야 caller 에게 제어권이 넘어감- 그 전까지는 제어권을 쥐고 있음
비동기 함수
return
호출되면 호출자(Caller)에게 제어권 넘어감return
이 호출되기 전에도 쓰레드의 제어권을Suspend
형식으로 포기 가능- 시스템에게 스레드의 제어 권한을 넘긴다는 의미
Suspending
이 일어날 수도 있고 안 일어날 수도 있는 지점을 알려주는 키워드가await
- 주의
await
에서는 반드시 suspending이 일어난다 → X, 안 일어날 수도 있다 → Oawait
에서는 단 한 번만 suspending이 일어 날 수 있다. → X, 여러번 일어날 수 있다 → O
- 주의
Suspend가 일어나면 발생하는 일
await
가 있는 부분에서 suspend가 발생할 수 있는데, 그 때 시스템으로 제어권이 넘어감- “시스템아, 난 오래 걸리니까 너 먼저 할 일 있으면 해, 대신 일 끝나면 나한테 다시 스레드 제어권 돌려줘”
- 이전 스레드와 다른 스레드를 받을 수도 있음
- “시스템아, 난 오래 걸리니까 너 먼저 할 일 있으면 해, 대신 일 끝나면 나한테 다시 스레드 제어권 돌려줘”
- 다시 resume 할 수 있도록 시스템에 예약을 걸어 놓음
- resume되면 await 이후로 돌아와서 나머지 일을 처리
- 이것이 기술적으로 어떻게 가능한지는 mutate 상태를 보호하는 방법에 대한 학습 필요
- →
Actor
키워드를 학습해야함
4. async/await 요약 정리
- async는 함수를 suspend 시킬 수 있음
- 해당 함수의 호출자(Caller)도 suspend 시킴
- async 함수안에
await
는 suspend를 실행할 수도 있음 - suspend 상태에서 Thread-safe가 아니기 때문에 다른 일을 먼저할 수 있다.
- 시스템이 다른 일을 예약
async
함수보다 늦게 시작되도 먼저 실행 가능
- 기다렸던 비동기 호출이 완료되면,
await
다음부터 원래 함수 실행
5. Task 훑어보기
- 사용법
- 동기적인 코드 흐름일 때,
Task { }
내부에서는 비동기 코드가 들어갈 수 있다고 알려주는 유닛 - 함수 자체가 비동기 함수(async 함수)일 땐 사용하지 않는다.
- 동기 함수(일반 함수)안에서 사용
- 동기적인 코드 흐름일 때,
- 역할
- { } 내부를 패키징 해서 시스템에 다음 가용 쓰레드에서 즉시 실행할 수 있도록 보냄
- 마치
DispatchQueue.global().async
와 비슷
6. Continuation 은 무엇일까?
- 각종 SDK 안에 있는 컴플리션 핸들러들은 async 함수로 많이 변경이 되고 있음
- ex) URLSession.dataTask -> URLSession.data
- 그러나 아직 바뀌지 않은 경우나, 기존 프로젝트의 일반 코드를 async 함수로 변경을 도와주는 친구
- Completion Handler 코드의 연장선으로써 resume 될 때 모든 상황을 다루어야함
// Existing function
func getPersistentPosts(completion: @escaping ([Post], Error?) -> Void) {
do {
let req = Post.fetchRequest()
req.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
let asyncRequest = NSAsynchronousFetchRequest<Post>(fetchRequest: req) { result in
completion(result.finalResult ?? [], nil)
}
try self.managedObjectContext.execute(asyncRequest)
} catch {
completion([], error)
}
}
// Async alternative
func persistentPosts() async throws -> [Post] {
typealias PostContinuation = CheckedContinuation<[Post], Error>
return try await withCheckedThrowingContinuation { (continuation: PostContinuation) in
self.getPersistentPosts { posts, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: posts)
}
}
}
}
결론
- async/await 아무렇게 쓰지 말고 알고 쓰자.
- 아무렇게나 블로그, 스택오버플로우 코드를 긁어쓰다가 에러처리에 이틀 소모
- “아~ WWDC와 공식문서는 신이구나” 하고서 공부에 돌입했는데, 시청과 필기 까지 3시간 30분 소요
- 영어 실력이 늘면 더 빨리 학습 할 수 있음
- 3시간 30분 아끼려다가 이틀씩 날리고 현타가 옴
출처
WWDC21 - “Meet async/await in Swift”
더 알아보기
- async sequence
- withTaskGroup(Structured Concurrency)
- actor(await, suspending 걸릴 때 시스템이 다른 작업을 할당할 수 있는 이유)
'생각정리 > 번역글(blog, WWDC)' 카테고리의 다른 글
[WWDC] Background Task (0) | 2022.10.27 |
---|---|
[번역] iOS 에서의 클린아키텍쳐와 MVVM (1) | 2022.09.14 |
CGD, Dispatch Source(feat. Event) (0) | 2022.09.01 |
[번역]UI는 왜 Main Thread 에서 업데이트 되어야 하는가?(feat. 데드락) (2) | 2022.09.01 |
[번역] 스위프트에서 재사용 가능한 이미지 캐시 (0) | 2022.08.30 |
Comments