WWDC 스터디 이번 주제는 async/await!
영상보다 내용이 적을수도,, 간단하게 적어보겠습니다
동기/비동기 코드를 제대로 작성해본 적 없는 나로써는 처음엔 좀 헷갈리는 주제였다.
먼저 동기와 비동기에 대해 알아보자!
동기 (synchronous) : 동시에 일어난다는 뜻, 요청과 결과가 동시에 일어난다. → 요청을 하면 시간이 얼마가 걸리든 요청한 자리에서 결과가 주어져야 함 (설계가 간단하고 직관적이지만 아무것도 못하고 대기해야 하므로 비효율적일 수도 있다.)
비동기 (asynchronous) : 동시에 일어나지 않는다는 뜻, 요청과 결과가 동시에 일어나지 않는다. → 결과가 주어지는데 시간이 걸리더라도 그 시간동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있음
UIKit의 UIImage에서 썸네일을 형성하는 기능 (fetchThumbnail
)
여기서는 fetchThumbnail
이 아래 두 함수를 호출한다고 가정
동기 : preparingThumbnail
호출 → 스레드 차단 + 해당 함수가 완료되기를 기다림 = 완료될 때 까지 다른 작업 수행 불가
비동기 : prepareThumbnail
호출 → 스레드가 자유롭게 다른 작업을 수행할 수 있음
** 비동기 버전의 함수가 작업을 완료하면 completionHandler
를 호출하여 알려준다.
비동기 함수가 작업이 완료됐음을 알리는 방법에는 여러가지가 있다. (completion handler, delegate callbacks)
✅ 비동기 함수를 호출하면?
1. 스레드가 빠르게 차단 해제된다.
2. 작업을 시작한다.
= 비동기 함수가 실행되는 동안 스레드는 다른 작업을 수행할 수 있음!
(아마도, 함수 호출하면서 차단된 스레드를 바로 해제해서 다른 작업도 수행할 수 있게 하는 것이 아닐까... 확실하게 아시는 분은 댓글 부탁드립니다..🥲)
item list가 있고 각 행에는 서버에 저장된 이미지의 썸네일이 표시된다.
해당 list에 표시할 썸네일을 준비해야 할 때 fetchThumbnail
메소드 호출, 일련의 단계를 통해 문자열을 UIImage
로 변환
thumbnailURLRequest
메소드 : 문자열에서 URLRequest
생성URLSession
의 dataTask
메소드 : 해당 request에 대한 데이터를 가져옴UIImage(data:)
: 해당 데이터에서 이미지를 생성UIImage
의 prepareThumbnail
메소드 : 원본 이미지에서 썸네일을 랜더링 (비동기 함수!)위 작업 과정은 각각 이전 작업의 결과에 따라 다르므로 순서대로 수행해야 함!
작업들 중 일부는 값을 빠르게 반환 (ex. String → URLRequest
, data → UIImage
)
따라서 함수가 발생하는 스레드에서 실행하고 이러한 작업이 동기적으로 호출되도록 하는 것이 좋음
But,
URLRequest → data
, UIImage → UIImage
와 같은 작업들은 시간이 좀 걸림
그렇기 때문에 비동기 함수를 제공하는 것임
즉, 순서대로 수행해야 하는 작업 중 시간이 오래 걸리는 작업도 포함되어 있을 수 있으므로 비동기 함수도 필요한 것
함수는 인수로 첫번째 작업에 대한 입력인 String
과 호출자에게 출력을 다시 제공하는 데 사용되는 completion handler
를 사용한다.
fetchThumbnail
이 호출되면 먼저 thumbnailURLRequest
를 호출한다.
(thumbnailURLRequest
= 동기 함수 = completion handler 필요 없음)
다음으로 URLSession
인스턴스에서 dataTask
를 호출, 해당 URLRequest
와 completion handler
를 전달
비동기 작업을 시작하기 위해 재개(resume)되어야 하는 URLSessionDataTask
를 동기적으로 생성
fetchThumbnail
이 반환되고 스레드는 다른 작업을 자유롭게 수행할 수 있음
✅ 정리
1. fetchThumbnail
호출
2. thumbnailURLRequest
호출
3. dataTask
호출하여 URLRequest
와 completion handler
전달
4. dataTask
를 시작시켜 주기 위해 resume()
메소드 사용
** 이 때 기본적으로 모든 작업은 일시정지 상태임, dataTask를 실행시켜 주기 위함
앞서 말했듯 이미지를 다운로드하는 데에는 시간이 걸리지만 데이터가 스트리밍되기를 기다리는 스레드를 차단시키고 싶지도 않기 때문에 매우 중요한 작업임!
이 과정의 결과로는 이미지 다운로드 성공 or 문제 발생
어느 쪽이든 요청은 완료되고 dataTask
에 전달된 completion handler
가 데이터, 응답, 오류와 같은 몇 가지 optional value와 함께 호출됨
** 문제가 발생하면 당연히 오류를 전달 //completion(nil, error)
모든 것이 해결되면 UIImage(data:)
를 사용하여 데이터를 이미지로 변환
이미지가 생성되면 UIKit
의 prepareThumbnail
메소드를 호출하여 completion handler
를 전달
이 작업이 완료되는 동안 스레드는 차단 해제되어 다른 작업을 수행할 수 있다.
썸네일 준비에 성공하면 이미지와 함께 completion handler
호출
그렇지 않으면 nil
호출
//completion(thumbnail, nil)
fetchThumbnail의 호출자는 fetchThumbnail이 실패하더라도 작업이 완료되면 알림을 받을 것임
But,
guard let return
을 작성하는 데 너무 익숙해서 completion handler
를 2번이나 까먹고 작성하지 않음
- 데이터 → 이미지 변환
//UIImage(data: data!)
- 썸네일을 준비하는 곳
//prepareThumbnail(of: CGSize~~~)
위 두 부분에서 completion 호출 없이 return만 있음.
따라서 작업을 실패하더라도 fetchThumbnail
의 호출자에게 알림이 전송되지 않고 행도 업데이트되지 않음
그렇기 때문에 무슨 일이 있어도 호출자에게 알리는 것이 매우 중요함!!
(↑ 누락된 completion 추가해줌)
하지만 여기서는 Swift의 일반적인 error handling 메커니즘
을 사용할 수 없음
문제가 발생하면 completion handler
내에서 오류를 throw할 수 없기 때문임
즉, Swift가 우리의 작업을 확인할 수 없다는 뜻
Swift에게 fetchThumbnails와 같은 completion handler
는 closure
에 불과함
클로저에 불과하다?
= 클로저는 명시적인 호출이 필요하므로 항상 호출되도록 강제할 방법이 없다!
그래서 방금 두 개의 guard 문에서 그냥 return 했을 때 컴파일 오류가 발생하지 않았던 것임. 강제할 방법이 없다는 것.
= completion handler
가 호출되는지 확인하는 것은 사용자에게 달렸다.
✅ 정리!
completion handler
사용하여 비동기식result type
사용 : .failure(error)
** 이 방법은 안전하긴 하지만 식을 추가하는 바람에 코드가 더 못생기고 길어짐
함수는 여전히 문자열을 인수로 사용하지만 지난 번과 달리 completion handler는 전달하지 않음
대신! 비동기식 함수임 (async
키워드와 함께)
함수를 비동기로 표시할 때
async
키워드의 위치
throws
바로 앞- 함수가 throw하지 않을 때에는
-> (화살표)
앞
이렇게 함수를 async
로 표시하면 함수와 함수의 특징이 간단해짐
성공적으로 썸네일이 준비되면 썸네일이 반환되고
그렇지 않고 오류가 발생하면 그냥 던져짐
fetchThumbnail
호출thumbnailURLRequest
호출URLSession.shared
에서 data(for: request)
를 호출, 데이터 다운로드 시작함
data
vsdataTask
- 둘 다 비동기식
- async/await 지원 여부 차이
data
는awaitable
!
→ 따라서 호출된 후에 스레드를 차단 해제하여 빠르게 일시 정지됨, 그러면 다른 스레드는 다른 작업을 자유롭게 수행할 수 있음 (데이터를 받아오는 동안 data 메소드 일시 정지, 스레드는 다른 작업을 수행하는 것)
이때, data
메소드가 throws
로 표시되어 있기 때문에 try
키워드 필요함
이전 버전에서 오류를 확인하고 명시적으로 completion handler
를 호출해야 했던 것 기억나시나요? 지금은 try
키워드로 모두 요약됨!
throw
로 표시된 함수를 호출하려면try
필요
마찬가지로,
async
로 표시된 함수를 호출하려면await
필요
여러 비동기 함수 호출이 있어도try
,await
는 한번만 작성하면 됨!
즉, 함수 호출은 try await
을 붙이자!
(키워드 순서 : async throws - try await , 차례대로 오는 것이 아니니 헷갈리지 말기!)
data
메소드는 재개되고 fetchThumbnail
로 돌아감이 시점에서 data
메소드가 return or throw하는 오류값이 유입됨
오류 발생 시 fetchThumbnail
이 오류를 발생시키고 그렇지 않으면 data와 response 변수가 정의됨
= 이전 버전의 dataTask
메소드에 전달된 completion handler
가 호출되었을 때 발생한 것과 유사함
두 버전 모두 URLSession
의 비동기 함수에서 발생하는 값과 오류가 유입되었는데 현재 버전은 훨씬 간단함!
왜냐면 우리가 의미하는 바를 정확히 말해주기 때문임
요청을 하고 반환된 값을 변수에 할당하여 사용할 수 있게 하고 문제가 발생하면 오류를 발생시킴
다운로드한 데이터에서 UIImage
생성 시도, 성공하면 썸네일 프로퍼티에 액세스하여 해당 이미지에 대한 썸네일이 렌더링됨
이때 썸네일이 렌더링되는 동안 다른 작업을 자유롭게 수행할 수 있음!
(썸네일 프로퍼티가 재개되어 fetchThumbnail
로 돌아갈 때까지)
썸네일이 렌더링되면 fetchThumbnail
이 이를 반환, 그렇지 않으면 오류 발생
** 전체코드 ↓
completion handler
버전과 달리 썸네일이 렌더링되지 않으면 Swift는 여기에 오류가 발생하거나 값을 반환하도록 함!
✅ 정리
completion handler
버전이 이전에 수행한 작업을 정확히 수행return
or throw
하여 함수가 완료되면 항상 호출자에게 알리고 있음 (try를 사용하여 반환, 동기식 코드는 throw 사용함)→ async/await
로 비동기 코드를 변환하여 더 안전하고 짧게, 의도를 잘 반영할 수 있도록 하는 방법의 한 예시일뿐!
properties
뿐만 아니라 initializers
도 마찬가지!사실 썸네일 프로퍼티는 SDK의 일부가 아닌 직접 추가한 것임
UIImage
의 extension에서 정의
CGSize를 형성하고 .byPreparingThumbnail(ofSize:)
에 전달한 결과를 기다림
(= await)
getter
setter
가 없음함수, 프로퍼티, 생성자(initializer)
에서
스레드를 차단 해제할 수 있는 위치를 나타내기 위해
표현식에서 await 사용 가능
await 키워드를 사용할 수 있는 또 다른 장소는 for문
이 부분은 잘 이해가 안 가서... 그냥 사용할 수 있구나~ 정도만 하고 넘어가겠습니다.. 혹시 잘 아시는 분은 댓글 부탁드립니다~!
여기까지 비동기 코드 예시와 async/await
사용 예시를 간단하다면 간단하게 살펴보았습니다!
※ 약간.. 제멋대로 이해하고 적어본 거라.. 혹시 잘못된 부분이나 오타가 있다면 알려주시고 헷갈리는 부분은 언제든지 같이 이야기 나눠보아요! 🤗
※ 너무 길어질 것 같아서 영상의 뒷부분은 2번째 포스팅 참고해주시면 감사하겠습니다.🤓
※ 현재 포스팅은 대략 15:02 까지의 내용입니다.
🎥 WWDC 영상 링크 : https://developer.apple.com/videos/play/wwdc2021/10132/