지난 포스팅인 커스텀 아키텍처에서 비동기 방식으로 사용 되었던 async/await 간단 설명 및 관련 개념들을 복습하는 시간을 가져보려 합니다.
우선 동기와 비동기 개념부터 살펴보겠습니다.
동기(synchronous)
작업이 동기적으로 실행하면, 현재 작업이 끝날 때까지 다른 작업들은 기다린다. 즉, 요청을 보낸 후 응답을 받아야지만 다음 동작이 이뤄집니다.
그래서 작업이 순차적으로 완료되어 순서가 보장되지만 하나의 작업이 오래걸리면 다른 작업들이 지연될 수 있죠.
비동기(asynchronous)
반대로 비동기로 작업을 실행한다면, 동시에 실행되어 해당 작업의 끝난 여부와 상관없이 기다리지 않고 바로 다음 작업을 계속 진행할 수 있다. 그래서 네트워킹 작업이나 이미지 로딩, 파일 읽기 등 시간이 오래 걸리는 작업들은 대부분 비동기로 처리됩니다.
그렇지 않으면 화면이 버벅거리거나 일시적으로 멈쳐보여 유저가 화면을 보지 못하는 이슈가 있을 수 있기 때문이죠!
CompletionHandler를 통한 비동기 처리 문제점
비동기로 작업을 처리하면 다른 작업이 수행하는 동안 현재 작업이 언제 끝나는지 언제 알 수 있을까요?
고전적인 방식인 Swift의 클로저를 통해 completionHandler라 불리는 것으로 해당 시점을 알 수 있게 됩니다.
물론 많이 사용하는 방식이지만, 클로저를 사용하게 되면 여러 문제들을 직면할 수 있습니다.
- 코드의 가독성이 떨어진다.
- 분산된 코드로 에러처리가 복잡해진다.
하나씩 살펴보면 비동기 코드를 작성하면 개발자는 completionHandler를 통해 개발자는 작업이 끝난 시점을 명시해줘야 합니다.
어디서 작업이 끝나는지 클로저로 되어있어 직관적이지 않을 뿐더러 컴파일러가 여기 종료 시점이 작성되지 않았어! 라고 잡아주지 않기 때문에 아래 코드는 5개의 잠재적 버그(휴먼 에러)를 발생시킬 수 있는 지점이 있습니다.
개선된 ResultType으로 성공/실패를 명시하여 가독성이 조금 개선된 느낌이지만 종료 지점을 completionHandler 미호출 할 수 있는 암시적 버그 가능성은 여전합니다.
async / await
클로저 대신 async/await를 사용하면 어떤 이점이 있을까요. 대충 살펴봐도 확실히 직관적이고 코드량도 좀 더 짧아졌죠.
비동기 코드를 동기 코드처럼 선언형으로 작성되어 한줄씩 순차적인 흐름으로 읽을 수 있게 되었습니다.
위의 문제 지점이였던 부분을 해결할 수 있게 되죠.
- 짧고 직관적인 코드로 가독성이 향상되었다.
- 에러처리가 명시적이고 더욱 간편하게 작성될 수 있다.
그래서 저도 커스텀 아키텍처에서 네트워크 처리를 async/await로 간결하게 사용했었죠 !
UI 바인딩 처리나 복잡한 관찰 객체가 필요한 부분이 아니라면 저도 이를 자주 사용하는걸 선호합니다.
사용방식
비동기 처리가 필요한 함수 뒤에 async 키워드를 붙이면 사용할 수 있고 에러를 반환한다면 추가적으로 throws 를 붙여야 합니다.
또한 비동기 코드는 동시(concurrent) 컨텍스트에만 실행됩니다.
그래서 fetchThumbnail()가 비동기 함수이기 때문에 URLSession의 비동기 함수 try await 키워드로 명시해 호출 가능한 것이죠.
만약 비동기 함수가 아니라면 대략 이런식으로 Task { } 로 감싸 수동으로 concurrent context를 제공하여 내부에서 사용해야 합니다.
동시성 (Concurrency)
동기와 비동기를 학습하면 따라오는 개념인 동시성은 이름도 비슷해서 저도 취준할 때 차이점을 늘 헷갈렸는데 분명히 다른 개념입니다!
우선 동시성은 논리적인 관점에서 여러 작업을 동시에 실행하는 것처럼 보이는 것을 의미합니다. 실제론 동시에 실행되진 않고 동시에 실행되는 것처럼 보이죠. 싱글코어나 멀티 코어에서 멀티스레딩을 하기 위해 사용됩니다.
비동기는 하나의 작업에 대한 처리방식이라면, 동시성은 좀 더 구조적인 접근 방식으로 여러개의 작업을 처리하기 위한 방식입니다.
즉 비동기(async)는 동시성(Concurrency)의 일부 방식일 수 있죠!
제가 이해한 바로 좀 더 쉽게 설명하기 위해 GCD 관점으로 설명하겠습니다.
(다음 포스팅에서 GCD와 Swift Concurrency 차이점 및 성능 비교 글을 작성해야겠네요 아마 미래의 제가...)
먼저 Concurrent Queue는 앞서 설명한 동시성의 특징을 갖고있는 큐 입니다.
다른 여러 개의 스레드에서 처리하는 큐로 순서는 중요하지 않고 빠르게 동시적으로 작업이 수행되야 할 때 사용됩니다.
예를 들어 인스타 게시물 같은 피드를 내릴 때 어떤 피드가 먼저 나오는지는 중요하지 않죠!
일단 빠르게 유저에게 화면을 보여줘야 하는게 우선이기 때문에 해당 큐를 사용하면 빠르게 데이터를 받아올 수 있습니다.
직렬성(Serial)
빠르게 데이터를 받아오는건 좋은데 순서가 보장되어야 하는 경우도 존재하겠죠.
만약 은행을 거래할 때 잔액이 없는 경우 동시에 입금과 출금이 이뤄진다면 아마 큰일이 나겠죠...?
입금이 먼저 이뤄져 잔액을 채운 후 출금이 되어야 합니다.
이처럼 순서를 보장하고 싶다면 Serial Queue를 사용하면 됩니다.
병렬성 (Parallel)
앞서 동시성은 논리적인 관점에서 여러 작업을 동시에 실행하는 것처럼 보인다고 설명했었죠.
반면 병렬성은 실제 물리적인 멀티 코어 환경에서 실제로 동시에 여러 작업이 수행되는 것을 의미합니다.
하드웨어의 물리적 능력을 직접적으로 활용하기 때문에 단일 코어에선 불가능하고 여러 개의 코어가 필수적입니다. 그래서 코어가 많을 수록 병렬적으로 여러 작업을 처리할 수 있기 때문에 좋은 컴퓨터 사양일수록 코어가 많고 가격도 비쌉니다.
위 사진 예시에서 동시성(Concurrency)은 단일 코어만 보여있지만 멀티 코어 환경에서도 가능합니다.
동시성은 한 작업을 동시에 다루는 구조에 관한 것이고, 병렬성은 작업을 동시에 실행하는 것에 초점이 맞춰져있죠!
저보다 똑똑한 Claude에게 동시성/병렬성 차이점을 물어보며 마무리 하도록 하겠습니다.
참고자료
https://sujinnaljin.medium.com/swift-async-await-concurrency-bd7bcf34e26f
https://bbiguduk.gitbook.io/swift/language-guide-1/concurrency
https://sujinnaljin.medium.com/ios-차근차근-시작하는-gcd-4-a621eca0a1d2
일욜 불 태웠따 ......
'Swift' 카테고리의 다른 글
[Swift] 프로퍼티 (Properties) (0) | 2022.09.14 |
---|---|
[Swift] 접근제어자 (Access Control) (0) | 2022.05.16 |
[Swift] inout parameter (0) | 2022.01.05 |
[Swift] 타입 캐스팅(Type Casting) - in / as 키워드 (0) | 2021.11.27 |
[Swift] Optinoal이란? Wrapping/Unwrapping (0) | 2021.10.02 |