-
[iOS/Swift] GCD Queue야매 iOS 2023. 8. 6. 18:30
GCD에서 큐는
- 메인
- 글로벌
- 프라이빗 (커스텀)
Main Queue
메인 큐는 iOS 내부에서 하나만 존재한다.
- 메인 스레드에 작업을 할당하는 큐
- 시리얼 큐
- DispatchQueue.main으로 접근
글로벌 큐
QoS로 6가지 종류로 나뉨
- QoS는 주로 우선순위로도 불린다.
- 기본 설정은 concurrent
DispatchQueue.global은 여러 개의 스레드로 작업을 처리한다.
- 여러 스레드를 생성한다.
QoS로 작업의 우선순위를 매겨 높은 우선순위를 가지는 작업에 더 많은 스레드를 배치해 많은 리소스를 사용하도록 한다.
- 우선순위가 높은 큐를 더 빨리 처리한다.
작업의 우선순위
개발 작업에 우선순위를 부여할 수 있다
DispatchQueue.main.async(qos: .userInteractive) { // task }
아래의 케이스도 발생할 수 있다.
큐의 우선순위와 큐에 들어간 작업의 우선순위가 다를 수 있다.
- 이땐 큐의 qos가 작업의 qos에 영향을 받아서 작업의 qos로 상승한다
→ 이 상황을 우선순위 역전 (priority inversion)이라고 한다.
높은 우선순위의 작업을 먼저 꺼내야 하기 때문에 큐의 앞에 있는 작업들의 우선순위가 상승한다.
let queue = DispatchQueue.global(qos: .background) queue.async(qos: .userInteractive) { // task }
프라이빗 (커스텀 큐)
기본 설정은 serial
큐의 QoS 설정 가능
queue 생성 let queue = DispatchQueue(label: "com.!!!.xxx") let queue = DispatchQueue(label: "com.!!!.xxx", attributes: .concurrent) let queue = DispatchQueue(label: "com.!!!.xxx", attributes: .concurrent, qos: .userInitiative)
메인 스레드와 UI
메인 스레드에서 UI 업데이트가 진행되어야 한다.
BUT WHY?
그 이유를 알기 위해 Objective-C에서 쓰이는 atomic과 nonatomic에 대해 알아야 한다
atomic vs nonatomic
atomic
- 유효한 값임을 보장한다 (값이 정확하지 않을 수도 있다)
- 여러 스레드가 동시에 값을 읽고 쓸 수 있다
- 값을 get / set 할 때 locking과 unlocking 하는 과정을 하게 되는데 이 과정에서 성능 저하가 발생할 수 있다
nonatomic
- 값이 유효한 값이라는 보장이 없다
- 정확한 값일 수도, 일부 수정된 값일 수도, 쓰레기 값이 들어있을 수도 있다
- 프로퍼티에 접근할 때의 속도는 좋아질 수도 있다.
뷰와 nonatomic
UIKit의 대부분 컴포넌트 nonatomic하다.
뷰와 멀티스레드
UIKit은 기본적으로 nonatomic하다
- 멀티스레드로 뷰에 접근 한다면 무수히 많은 사이드 이펙트가 발생할 수 있다
스레드세이프
그럼 스레드세이프 하게 하면 되지 않나? 안됨 버거움
스레드 세이프하게 만들기 위해 아래와 같은 방식만 쓰면 모든 것이 해결될 것 같지만 그렇지 않음
- nonatomic → atomic
- NSLock 적용
정책적인 한계점
- 비동기로 뷰의 속성을 변경했을 때 그 값은
- 모든 스레드가 동일한 시간에 적용되어야 하는지
- 각 스레드의 RunLoop에 따라야 하는지
- UITableView에서 한 스레드가 셀을 제거하고 다른 스레드가 인덱스로 셀에 접근한다면 크래시
- 백그라운드 스레드가 자식 뷰를 부모 뷰로부터 제거했지만 런루프가 끝나지 않아 해당 사항이 업데이트 되지 않은 상황에서 유저가 자식 뷰를 터치했을 때
- 반응해야 하는지
- 반응하지 않아야 하는지
런루프와 뷰 드로잉 사이클의 한계점
뷰는 UIApplication가 메인스레드에 초기화한 Main RunLoop에 의해 처리된다.
- application이 활성화되어 있는 동안 RunLoop가 이벤트를 처리한다.
- 현재 RunLoop의 마지막에 뷰가 다시 그려진다. (View Drawing Cycle)
- 모든 뷰의 변화가 처리되고 모든 변경사항이 한번이 활성화 된다.
→ Main RunLoop가 화면을 refresh 한다.
렌더링 프로세스의 한계점
렌더링 프로세스
UIKit
- 모든 컴포넌트 포함
- 유저 이벤트 처리
Core Animation
- drawing, displaying and animating 담당
OpenGL ES
- 2D와 3D 렌더링 서버
Core Graphics
- 2D 렌더링 서버
하드웨어
- GPU
Commit Transaction
- 뷰 레이아웃, 이미지 디코딩, 이미지 포맷 변경
- 뷰 레이아웃 searilize해서 Render Server에 전달
Render Server
- Commit Transaction으로부터 받은 데이터를 deserailize 해서 렌더링 트리로 변환
- 뷰의 레이어 프로퍼티로부터 drawing instruction 생성
- VSync Signal이 왔을때 openGL을 호출해 스크린을 렌더링한다
GPU
- OpenGL의 렌더링 파이프라인을 이용해 렌더링하고 이 과정에서 나온 결과물을 버퍼로 이동한다
Display
- 버퍼로부터 데이터를 가져와 스크린에 보여준다.
하지만 여러 스레드가 UI를 업데이트 하면 더 많은 커밋 트랜잭션을 처리해야 하므로 GPU가 많은 리소스를 사용하게 되고 최악의 경우 완료하지 못할 수도 있다.
💡 뷰를 여러 스레드에서 업데이트 하는 것에 대한 이점이 별로 없다.
→ 시리얼 큐로 UI를 동기적으로 업데이트 하면 별 무리가 없을듯
강의
https://www.inflearn.com/course/iOS-Concurrency-GCD-Operation'야매 iOS' 카테고리의 다른 글
[iOS/Swift] 동시성 문제 (0) 2023.08.06 [iOS/Swift] DispatchGroup / Dispatch Work Item (0) 2023.08.06 [iOS/Swift] 동시성 프로그래밍 (0) 2023.08.06 [iOS/Swift] Struct에 대해 알아보기 (0) 2023.04.19 [iOS/Swift] Combine 알아보기 ix (0) 2023.03.26