ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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)
    

    https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html


    메인 스레드와 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 적용

    정책적인 한계점

    • 비동기로 뷰의 속성을 변경했을 때 그 값은
      1. 모든 스레드가 동일한 시간에 적용되어야 하는지
      2. 각 스레드의 RunLoop에 따라야 하는지
    • UITableView에서 한 스레드가 셀을 제거하고 다른 스레드가 인덱스로 셀에 접근한다면 크래시
    • 백그라운드 스레드가 자식 뷰를 부모 뷰로부터 제거했지만 런루프가 끝나지 않아 해당 사항이 업데이트 되지 않은 상황에서 유저가 자식 뷰를 터치했을 때
      1. 반응해야 하는지
      2. 반응하지 않아야 하는지

    런루프와 뷰 드로잉 사이클의 한계점

    뷰는 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

Designed by Tistory.