iOS/개념

[iOS] iOS에서의 Thread, GCD, NSOperation

누알라리 2021. 12. 8. 23:21

등장하는 개념들 정리한 포스트 참고해주세요~

https://hyunndyblog.tistory.com/177

 


iOS의 Thread

Thread는 두 가지 종류가 있다.

  • Main Thread
  • Global Thread (= Background Thread)

 

Main Thread

iOS에서 Main Thread는 오직 한 개만 존재한다.

→ 나머지는 모두 Background Thread

 

개발자가 일반적으로 작성한 코드는 Main Thread에서 동작한다. 이유는 작성된 코드가 Cocoa에서 실행되는데, 이 Cocoa가 코드를 Main Thread에서 호출하기 때문이다.

(※ Cocoa: OS X, iOS 애플리케이션을 개발할 때 사용하는 프레임워크를 모두 포함하는 개념)

 

 

Main Thread는 interface Thread라고도 불린다.

유저가 interface에 접근하면 이벤트가 Main Thread에 전달되고, 개발자가 작성한 코드는 이에 반응해야 한다.

따라서 UI와 관련된 작업은 반드시 Main Thread에서만 작성되어야 한다.

 

Main Thread는 Main Queue에서 실행되는데, 이 Main Queue는 Serial Queue이다.

따라서 Main Thread에서 작업 시간이 오래걸리는 Task를 수행하면 화면이 정지된것처럼 멈춰있다.

 

Global Thread (Background Thread)

iOS의 framework들은 background에서 구동된다.

몸체는 background에 있고 가끔씩 Main Thread에 손(delegate, Completion Handler)를 뻗는다.

(※ Ex) animation, webView의 content, 음악 재생 etc)

 

 

화면에 나타나지 않는 작업은 모두 background Thread에 맡기고 delegate method나 callback 함수로 Main Thread에서 호출하여 이벤트를 컨트롤하는 것이 일반적이다.

 

 

Global Thread는 Global Queue에서 실행되며, 이 Global Queue는 Concurrent Queue이다.

Global Queue에서는 Task의 우선도를 선택할 수 있으며, 직접 선택이 아닌 QoS에 있는 QosClass에서 설정한다.

 

 

자동으로 Background에서 실행되는 작업들도 있지만, 명시적으로 Background에 코드를 작성해야하는 경우가 존재한다.

  1. code가 callBack은 되는데 Main Thread에 없는 경우 (= 개발자가 작성한 코드가 의도와 상관없이 background에서 실행되는 경우)
    1. Layer를 그리는 draw(_: in:)는 background Thread에서 호출된다는게 apple document에 나오고, callback 함수가 main thread에서 실행되는 것을 보장하지 않는다고 되어있다. 따라서 개발자가 background thread에서 작업이 되는 것을 알고 코드를 작성하는 것이 중요하다.
    2. AVFoundation Framework의 경우 apple 문서에 completion function과 notification이 background thread에서 실행될 수 있다고 나와있다. 만약 개발자가 이 두 function에서 UI 관련 로직을 업데이트 해야한다거나, main thread에 있는 value를 가져다 써야하는 경우 해당 부분을 명시적으로 main thread로 switch 시켜줘야 한다.
  2. code 수행에 지나치게 오랜 시간이 걸리는 경우
    • 수행이 오래걸리는 코드. 너무 오랜 시간 동안 Main Thread가 Block 되면 app이 강제 종료 될 수 있다.

 

Background Thread 관리가 힘든 이유

  • 한 property에 여러개의 thread가 접근할 경우 concurrent로 code가 실행되는 상황에서 난감하다. 하나의 code에 두 개 이상의 thread가 접근하지 못하도록 lock을 걸 수도 있지만, 방법이 어렵다.
  • thread의 lifetime이 객체의 lifetime과 관련성을 갖지 않는다. 이 말은 thread가 이미 deinit된 객체에 접근할 수 있다는 뜻이며, 운이 나쁘면 crash를 발생시킬 수 있고, deinit 되어야하는 객체를 deinit 되지 못하게 만들 수 있다.

 

iOS에서 Multi Threading 작업은 어떻게 하는걸까?

iOS에서는 Main Thread에 몰린 Task를 Queue에 보내기만 하면 다른 Thread들(Global Thread)를 적절히 생성해서 분배해준다.

Queue에 작업을 보내는걸 개발자가 하면,

스레드를 생성하고 작업을 분배하는건 어떻게 하는걸까?

  1. GCD
  2. NSOperation

GCD (Grand Central Dispatch)

(* Dispatch 뜻: 보내다, 파견하다)

C언어 기반의 저수준 API

가볍고 성능면에서 뛰어남

Block(Closure)로 구현되어 있어 가독성이 좋고 사용성 좋음

작업 취소, KVO, 재사용 등은 직접 만들어줘야함.

개념

GCD는 개발자가 Queue에 작업을 보내면 그에 따른 스레드를 적절히 생성해서 분배해주는 방법이다.

 

Queue에 작업을 "보내야" 하니까 GCD 방법에서 쓰이는 Queue의 이름이 DispatchQueue이다.

따라서 DispatchQueue에 작업을 추가하면 GCD는 작업에 맞는 스레드를 자동으로 생성해서 실행하고, 작업이 종료되면 스레드를 제거한다.

 

DispatchQueue에는 두 가지 Type이 존재한다.

App이 실행함과 동시에 구조적으로 두가지 Queue가 자동생성된다.

  • Main Queue
    • 오직 한 개만 존재
    • Serial 특성을 갖는 Queue
    • 이곳에 할당된 task는 Main Thread에서 처리됨 (UI 업데이트)
    • Main Queue에는 sync task를 추가할 수 없다. 교착상태에 빠질 수 있기 때문
    DispatchQueue.main.async {
    	// task
    }
    
  • Global Queue
    • Concurrent 특성을 갖는 Queue
    • QoS (Quality Of Service) 를 결정할 수 있음, 6종류로 나뉨
    • QoS에 따라 각각 다른 큐 객체를 생성한다. -> DispatchQueue.global(qos: .utility) Dispatch.global() 은 다른 큐!
    DispatchQueue.global().sync {
    	// task
    }
    
    DispatchQueue.global().async {
    	// task
    }
    
  • Private Queue (사용자 지정 큐)
    let myQueue = DispatchQueue.init(label: "my", qos: .background, attributes: .concurrent) myQueue.async { // task }

 

GCD를 쓸 때 주의할 점

  1. UI는 main Thread에서 처리한다.
  2. 메인 큐에서 다른 큐로 작업을 보낼 때 sync를 사용하면 안된다.
  3. 현재와 같은 큐에 sync로 작업을 보내면 안된다.
  4. 메인스레드에서 DispatchQueue.main.sync를 사용하면 안된다. (교착상태에 빠질 수 있다)
  5. task를 보내는 것 == 클로저를 보내는 것, 객체에 대한 캡처를 주의한다.

 

NSOperation

Object-C 언어 기반의 고수준 API

내부적으로는 C로 구현된 GCD를 고수준 언어로 Wrapping 한 것으로, GCD보다 무겁다.

작업 취소, KVO, 작업 재사용 등 GCD에 비해 고성능 기능을 제공한다.

개념

OperationQueue에 작업을 넣으면 스레드를 적절히 생성해 분배해준다.