각종 사이트, 대학 전공 강의자료를 정리해서 제작했습니다.
Swift를 처음 공부하는 것이므로, 오류를 알려주시면 달게 받겠습니다 🙂
1. 동기, 비동기?
- 영어로는 각각 Synchronous, Asynchronous이다.
동기
- 정의 그대로 동시에 일어난다는 것.
- Request(요청) 생성시 Result를 반환할 때까지 다른 Request를 처리할 수 없음.
비동기
- 동기와 반대로, 동시에 일어나지 않음을 의미한다.
- Request 생성시 Result를 반환할 때까지 다른 Request를 처리할 수 있음.
- 대신에 동기보단 Request → Result를 획득하는 속도가 느림
2. 그래서 비동기 처리는 실제 어디에 쓰이는가?
- 많은 상황에서 비동기 처리 방식이 쓰인다.
- 간단한 실생활의 비유하자면
- 우리가 관심있는 이성에게 카톡을 보내고 카톡이 오기만을 기다리며 알림을 보고만 있는 상황을 동기 처리라고 하자면…
- 동네 친구에게 카톡을 보내고 다른 일을 하다가 답장이 오면 그에 대한 답장을 하는 것이 비동기 처리라고 할 수 있다.
- 그렇다면, 개발 과정에선 언제 쓰일까?
- 우리가 휴대폰 어플로 네트워크 통신을 하는 과정이라고 생각하면
- 우리가 카카오톡으로 메시지를 보내는 과정에서 생각해보자.
- 카카오톡 메시지를 보내면 네트워크 연결이 어떻든 간에 나의 핸드폰 화면에는 내가 입력한 메시지가 표시된다.
- 메시지가 보내지면, 그에 따라 화면이 또다시 바뀌게 된다.
- 이와 같은 과정이 비동기적으로 이루어지지 않는다면 어떻게 될까?
- 메시지가 네트워크 상에서 정상적으로 보내지게 되면 그 후에 나의 카카오톡 화면에 뜨게 될 것이다.
- 해당 예시 외로, 휴대폰 화면에 앱 뷰를 표시할 때, 비동기 처리를 하지 않게 되면 버벅거림이 심하게 발생한다.
- iOS 앱은 주로 메인 스레드에서 UI 업데이트 및 사용자 이벤트를 처리.
- 이때, 비동기 처리를 하지 않은 코드가 메인 스레드에서 실행되게 된다면, UI 업데이트 및 사용자 입력에 대한 응답이 느려질 수 있음.
- 이러한 현상을 방지하기 위해 주로 비동기 처리를 사용하여 작업을 메인 스레드 밖에서 수행한다!
Thread(스레드)란 뭘까?
- 우리는 이전까지 강의에서 “코드, 데이터, 힙, 스택” 영역에 대해 수없이 학습했다.
- 그런데, 이를 통틀어서 뭐라고 할까?
-
더보기바로, “프로세스” 이다.
- 그게 뭔데?!
- 이는 “실행 중인 프로그램”을 의미한다.
- Operating system’s abstraction for a running program
- 즉, 실행하고 있는 것을 추상화 시킨 것
- 메모리에 올려진 데이터라고도 할 수 있다.
- processor, program과는 다른 것
- processor: CPU, 하드웨어
- program: 실행 가능한 파일
- 알아두면 좋은 용어 “Context”
- State information that the process needs to run including values in PC(program counter : 현재 실행하고 있는 인스트럭션[컴퓨터가 수행할 수 있는 명령어]의 메모리 주소를 의미), registers(cpu 안에서 상태 정보들을 저장할 수 있는 임의의 공간), and main memory(프로세스가 사용하고 있는 메모리 정보 자원들)
[시스템 프로그래밍, 운영체제 정리본이 아니므로 여기까지만 간단하게 이야기를 하자]
그래서 Thread(스레드)가 뭔데? (우선적으로 소프트웨어적으로 학습하자)
- 위에서 이야기한 프로세스의 실행 단위이다.
- 프로세스들 간에는 IPC를 통한 자원 공유를 할 수 밖에 없었다.
- 하지만 프로세스 내부의 thread들간에는 동일한 코드, 전역 변수를 공유할 수 있다.
- Memory 영역이 공유되기 때문!
- 다음과 같은 그림으로 살펴보자. -
프로세스의 실행 단위가 thread이고, thread들 간에는 code, data, files와 같은 영역들이 공유되는 것을 확인할 수 있다.
- 그래서 비동기 처리, iOS 공부하다가 갑자기 왠 CS 지식을 말하는가?
- 이에 대한 답변으로, 하나의 App은 하나의 프로세스가 도는 것이므로, 여러 스레드를 만들어 이전 설명한 비동기 처리를 진행할 것이므로 미리 이야기를 했다.
- 알아두면 좋은 것이 iOS에선 NSThread라고 불리는 클래스가 존재한다.(소프트웨어 스레드! → 뒤에서 나온다)
앱의 시작과정, 동작원리
- 슬슬 Swift에 적용하기 위해 빌드업을 해보자.
- 해당 과정에서 우리가 알아두면 좋은 지식이 있다. 바로 “앱의 시작(과정)과 화면을 다시 그리는 원리”이다.
- 앞서 계속 언급한 것처럼, 시작(과정)과 화면을 그리는 것은 “메인(1번) 쓰레드” 역할이다.
앱의 시작할 때
- 아직 해당내용에 대한 딥한 공부를 하기 전에, 간단히 요약하자면
- Launch Time, Running 두 개의 파트로 분류가 가능하다.
- Launch Time 분기에서는 main 함수 실행, 앱 객체 생성, 화면 준비 과정을 거치고
- Running 분기에서는 “Run Loop”라는 것을 생성한다.
앱이 실행중인 동안
- Run Loop가 뭔데?
- 앱의 main run loop는 모든 사용자 관련 이벤트를 처리
- UIApplication 객체는 실행시 main run loop를 설정하고, 이를 사용해 이벤트를 처리하고 UI 업데이트를 처리
- 이름에서 알 수 있듯이, main run loop는 앱의 main thread에서 실행되며, 사용자 이벤트가 입력되면 순차적으로 처리
- 사용자가 장비와 상호 작용하며 발생된 이벤트는 UIKit에 의해 설정된 특정 포트를 통해 앱에 전달. 이벤트들은 내부 queue에서 대기하고 있다가 main run loop에 하나씩 전달 된다.
- 다양한 유형의 이벤트를 iOS 앱에 전달 할 수 있음. 가장 일반적인 이벤트에는 터치, 원격 제어, 모션, 가속도계 및 자이로 스코프 이벤트 등이 있음. 각 이벤트에 대한 자세한 내용은 Event Handling Guid for UIKit Apps에서 확인 할 수 있음.
- 솔직히 무슨 말인지 확실하지 않게 느껴진다..
- 하지만, 어떻게든 흡수해보자면.. 앱이 실행되는 동안 메인(1번) 쓰레드가 화면을 띄우고, 메인 쓰레드에서 다양한 이벤트 처리도 계속해서 처리하도록 무한 루프가 돈다는 것으로 흡수했다.
- 추가로 강의 내용을 정리하자면
- 앱이 시작될때 앱을 담당하는 메인 런루프(반복문)가 생기며, 이는 이벤트 처리를 담당한다.
- 어떤 함수를 실행시킬 것인지 선택 / 실행한 이후
- 함수 등의 실행 결과를 화면에 보여줄때 필요하다면 다시 화면을 그리는 역할을 메인(1번) 쓰레드가 맡는 것!
아 이런 거 몰라도 돼? (실제로 이러진 말자) 하신다면
- 아무튼 메인 Thread는 화면을 다시 그리는 역할도 하기 때문에 너무 오래 걸리는 작업들을 시키면 안된다라고 이해하면 된다.
그렇다면…
Thread1 | (task1) | (task2) | (task3) | (task4) | (task5) |
⬇️ 분산처리를 어떻게 하는지에 대한 코딩 방법 | |||||
Thread2 | task1 | - | - | - | - |
Thread3 | task2 | - | - | - | - |
Thread4 | task3 | task5 | - | - | - |
Thread5 | task4 | - | - | - | - |
- 이렇게 분산처리를 어떻게 할지 코딩하는 것을 “비동기 처리 / 동시성 프로그래밍” 으로 할 수 있다.
동시성 처리
- 우리는 여기까지 오기 위해 다양한 지식을 흡입해야 했다.
그래서 iOS에서는 동시성을 어떻게 처리할 수 있지?
→ 다른 언어(javascript, flutter.. 등)을 배운 적이 없어서 모르지만, iOS는
💡 작업(Task)를 “대기행렬(Queue)”에 보내기만 하면, iOS(운영체제시스템)가 알아서 여러쓰레드로 나눠서 분산처리(동시적 처리)를 한다.
- 시스템 프로그래밍 시간 처럼 일일이 Thread를 안 만들어줘도 된다고?!
- 그냥 Queue로 보내기만 하면 알아서 Thread로 나누어 준다니..
- 들어오자마자 Queue의 FIFO 동작방식으로 인해 즉시 쓰레드에 배치한다.
- 즉, 우리는 작업(Task)을 Queue로 보내기만 하면 된다.
대기열의 종류
[iOS 프로그래밍에서는 대기열이 크게 2가지 종류가 있다.]
- DispatchQueue(GCD - Grand Central DispatchQueue)
- OperationQueue
[iOS 프로그래밍에서의 대기열 특징은 다음과 같다.]
- 직접적으로 쓰레드를 관리하는 개념이 아닌, Queue의 개념을 이용해서, OS에서 알아서 쓰레드를 관리한다.
- (쓰레드 객체를 직접 생성시키거나 하지 않는) 쓰레드보다 더 높은 레벨/차원에서 작업을 처리한다.
- 메인쓰레드가 아닌 다른 쓰레드에서 오래걸리는 작업들과 같은 작업들이 쉽게 비동기적으로 동작하도록 한다.
우선 여기까지만 정리하고, 이론적인 다른 내용부터 살펴보자.
병렬(Parallel), 동시성(Concurrency)
참고 글 : https://velog.io/@hth3396/하드웨어-스레드와-소프트웨어-스레드
hardware thread, software thread
일반적으로 운영체제에서 다루는 스레드는 CPU를 할당 받을 수 있는 작업의 단위이다. 그렇다면 흔히 PC를 구매할때 CPU의 스펙에 나오는 8코어 / 16스레드 와는 어떻게 다른 것일까 ? 하드웨어 스
velog.io
- 우선 앞서 스레드를 설명할 때, “소프트웨어” 스레드에 대한 설명을 진행했다. 그렇다면 하드웨어 스레드가 있다는 건데…
- 하드웨어 스레드란 코어에서 동시에 할 수 있는 작업의 갯수를 의미한다.
- 잉? 코어가 뭔뎁쇼
- 우리가 일반적으로 컴퓨터를 구매할 때 보는 8코어 / 16스레드에서의 코어를 의미하고, 이(코어)는 CPU 내부의 계산을 해주는 단위라고 볼 수 있다.
- 그럼 하드웨어 스레드, 소프트웨어 스레드 둘의 관계가 어떻게 되는데?
- 상황에 대한 예시는 위의 블로그에서 친절히 설명해준다.
- 요약하자면
- 하드웨어 스레드는 병렬적으로 몇개의 소프트웨어 스레드를 수행할 수 있을지를 결정한다.
- 용어 정리
- 다중 스레딩 : 여러개의 하드웨어 스레드로 구성되어 하나의 코어에서 여러 작업을 수행하는 일
- 멀티 스레딩 : 하나의 프로그램에서 여러 소프트웨어 스레드를 통해 작업을 분산시켜 수행함으로써 성능을 향상시키는 것
아니 그래서 언제 병렬, 동시성을 이야기할 거니?
- 위 글에 녹여져 있다고 생각한다. 그래도 다시 정리를 해보자면
- 병렬(Parallel)
- 하드웨어 쓰레드에서 실제 동시에 일을 하는 개념
- 내부적으로 알아서 동작하기 때문에 개발자가 신경쓸 필요가 없는 영역
- 동시성(Concurrency)
- 메인 쓰레드가 아닌 다른 소프트웨어적인 쓰레드에서 동시에 일을 하는 개념
- 이는 개발자가 신경써야 하는 영역이다 🤣
- 하드웨어 쓰레드를 알아서 switching하면서 빠르게 일을 처리
💡 따라서 앱을 만드는 과정에선, 소프트웨어적인 동시성 처리만 신경쓰면 된다. (물론 iOS OS를 뜯어보는 미래에는 … )
비동기 vs 동기
- 간단하게 그림으로 살펴보면 다음과 같이 볼 수 있다.
- CPU 제어권과 관련된 Blocking, Non-Blocking 개념도 첨가해 비교해보자면
Blocking (제어권 바로 반환하지 않음) |
Non - Blocking (제어권 바로 반환) |
|
동기 (결과값을 기다리고, 순차적 실행) |
![]() |
- |
비동기 (결과값을 기다리지 않고, 비순차적 실행) |
- | ![]() |
→ Swift는 동기는 Blocking의 개념으로, 비동기는 Non-Blocking의 개념으로만 사용된다.⭐️
- 아래의 글이 잘 정리되어 있는 것 같다. 추후에 Network에 대해 다룰 때 CS 지식도 얻을 겸 참고해보자.
https://didu-story.tistory.com/307
[Network] Blocking I/O 와 Non-Blocking I/O 에 대해서 알아보자
스터디를 운영하고 있습니다. 해당 repository에 가면 더 많은 정보를 보실 수 있습니다! https://github.com/JulSaMo/CS-start GitHub - JulSaMo/CS-start: 📝 CS 지식 대백과 ✨ 📝 CS 지식 대백과 ✨. Contribute to JulSaM
didu-story.tistory.com
https://didu-story.tistory.com/306
[Network] (Swift) Blocking과 Non-Blocking , Sync와 Asyn의 차이는?
스터디를 운영하고 있습니다. 해당 repository에 가면 더 많은 정보를 보실 수 있습니다! https://github.com/JulSaMo/CS-start GitHub - JulSaMo/CS-start: 📝 CS 지식 대백과 ✨ 📝 CS 지식 대백과 ✨. Contribute to JulSaM
didu-story.tistory.com
직렬(Serial) vs 동시(Concurrent)
- 해당 내용또한 아래의 그림을 통해 간단히 이해해보자.
- 아니, 처음부터 계속 분산처리에 대해 강조하는데… 그럼 언제, 왜 직렬(Serial)큐가 필요할까?
- 정답은 “순서가 중요한 작업”인 경우다.
- 비유하자면, 카카오페이로 선물하기 결제를 할 때
- 주문 생성
- 결제 정보 입력
- 결제 승인
- 주문 완료
- 위 단계와 같이 순서가 중요한 작업인 경우 사용된다.
GCD의 개념 및 종류
- 위에서 DispathQueue를 Grand Central DispatchQueue라고 부른다고 했다.
DispatchQueue에도 3가지 종류가 있다.
큐의 종류 | 생성 코드 | 특징 | 직렬/동시 | |
Dispatch Queue (GCD) -> class |
.main | DispatchQueue.main | 메인큐 = 메인쓰레드 (1번쓰레드) (UI업데이트 내용 처리하는 큐) |
Serial(직렬) |
.global() | DispatchQueue.global(qos: ) | 6가지 QoS(작업에 따라 QoS상승 가능) (시스템이 우선순위에 따라 더 많은 쓰레드를 배치하고, 배터리를 더 집중해서 사용하도록 함) |
Concurrent(동시) | |
custom | DispatchQueue(label: "...") | QoS 추론 / QoS 설정가능 | 디폴트: Serial - 둘다 가능, attribute로 설정 |
|
OperationQueue -> class (내부적으로 GCD기반으로 구현) |
let opQ = OperationQueue() | 디폴트: .background 기반(underlying) 디스패치큐에 영향 받음 (unspecified를 제외한 5가지) |
디폴트: Concurrent - 둘다 가능, maxConCurrentOperationCount로 사용할 쓰레드 갯수 설정 가능) |
[각각을 알아보자]
DispatchQueue.main
- 유일한, 직렬, 실제는 그냥 메인 쓰레드(1번 쓰레드)를 의미한다.
DispatchQueue.global()
- 종류가 여러개, 기본설정 시 동시성을 가짐. QoS에 따라 다른 성능을 가짐
- QoS(Quality Of Service)는 큐의 서비스품질로 아래 표와 같이 사용된다.
DispatchQueue(label: "...")
- 커스텀으로 만드는 큐로, 기본설정 시 직렬, QoS 설정 가능.
GCD 사용시 주의사항
- 아래와 같은 주의사항을 항상 염두하자⭐️
1. 반드시 메인큐에서 처리해야하는 작업
- UI 관련 일들은 다시 메인쓰레드로 보내야 한다!
// 글로벌 큐에서 UI 관련 작업 수행 시 에러 발생
DispatchQueue.global(qos: .utility).async {
...
self.textLabel.text = "New posts updated!"
}
// UI 관련 작업은 메인큐를 통해 메인쓰레드로 보내야함
DispatchQueue.global(qos: .utility).async {
...
DispatchQueue.main.async {
self.textLabel.text = "New posts updated!"
}
}
2. 컴플리션핸들러(completion handler)의 존재이유 - 올바른 콜백함수의 사용
: 우선 컴플리션 핸들러란 “어떠한 작업이 완료된 후 이후에 진행되는 작업을 담당하는 것”을 의미한다.
- 아무리 비동기 처리를 한다고 해도, 결국 비동기작업이 명확하게 끝내는 시점을 알고, 어떤 작업을 할 필요하다.
- 비동기적인 작업을 해야하는 함수를 설계할 때 return을 통해서 데이터를 전달하려고 할 시 항상 nil을 반환한다.
- 따라서 비동기적인 작업을 해야하는 함수는 항상 클로저를 호출할 수 잇도록 함수를 설계해야한다.
[참고] 왜 @escaping을 붙인 클로저를 사용해야 할까?
- 아래와 같은 코드 실행하는 상황을 가정해보면
- @escaping 키워드를 붙이지 않으면 비동기적으로 실행하는 부분이 값을 반환하기 전에 이미 getImages 함수는 종료되면서 날라가기 때문에 힙에 저장을 하기 위함.
func getImages(..., c...: @escaping (UIImage?) -> Void) {
...
URLSession.shared.dataTak(..) {
...
completion(photoImage)
}.resume()
}
출처
https://dev-coco.tistory.com/46
https://forums.developer.apple.com/forums/thread/109196
https://jerryjerryjerry.tistory.com/178
'iOS > Swift' 카테고리의 다른 글
제네릭(Generics) (0) | 2024.02.18 |
---|---|
비동기 프로그래밍(About Asynchronous) [2] (1) | 2024.02.17 |
Swift No.26 (0) | 2024.02.08 |
Swift No.25 (0) | 2024.02.03 |
Swift No.24 (0) | 2024.02.02 |