포스트

RunLoop와 DispatchQueue의 차이점

RunLoop란?

RunLoop는 스레드와 관련된 기본 인프라의 일부입니다.

RunLoop는 작업을 예약하고 들어오는 이벤트의 수신을 조정하는 데 사용하는 이벤트 처리 루프입니다. RunLoop의 목적은 할 일이 있을 때 스레드를 계속 바쁘게 유지하고, 할 일이 없을 때 스레드를 절전 모드로 전환하는 것입니다.

RunLoop 관리는 완전히 자동으로 이루어지지 않습니다.

적절한 시간에 RunLoop를 시작하고 들어오는 이벤트에 응답하도록 스레드 코드를 설계해야 합니다. 코코아와 코어 파운데이션은 모두 스레드의 실행 루프를 구성하고 관리하는 데 도움이 되는 실행 루프 객체를 제공합니다.

애플리케이션의 메인 스레드를 포함한 각 스레드에는 연결된 실행 루프 객체가 있으므로 애플리케이션에서 이러한 객체를 명시적으로 생성할 필요는 없습니다.

그러나 보조 스레드만 명시적으로 실행 루프를 실행해야 합니다. 앱 프레임워크는 애플리케이션 시작 프로세스의 일부로 메인 스레드에서 실행 루프를 자동으로 설정하고 실행합니다.

다음 섹션에서는 실행 루프에 대한 자세한 정보와 애플리케이션에 맞게 실행 루프를 구성하는 방법을 제공합니다. RunLoop 객체에 대한 자세한 내용은 NSRunLoop 클래스 참조CFRunLoop 참조를 참조하십시오.

RunLoop 해부하기

런 루프(Run Loop)는 이름에서 알 수 있듯이, 스레드가 들어가 이벤트 핸들러를 실행하여 들어오는 이벤트에 반응하는 루프입니다.

여러분의 코드는 실제 루프 부분인 런 루프를 구현하는데 필요한 제어문을 제공합니다. 다시 말해, 여러분의 코드는 이벤트 처리 코드를 ‘실행’하는 런 루프 객체를 사용하는 while 또는 for 루프를 제공합니다.

런 루프는 두 가지 유형의 소스로부터 이벤트를 받습니다.

입력 소스는 일반적으로 다른 스레드나 다른 애플리케이션으로부터 오는 메시지와 같은 비동기 이벤트를 전달합니다.

타이머 소스는 예정된 시간이나 반복 간격에서 발생하는 동기 이벤트를 전달합니다. 이 두 유형의 소스 모두 이벤트가 도착할 때 애플리케이션 특정 핸들러 루틴을 사용하여 이벤트를 처리합니다.

그림 3-1은 런 루프의 개념적 구조와 다양한 소스를 보여줍니다.

입력 소스는 비동기 이벤트를 해당 핸들러로 전달하고 스레드와 연관된 NSRunLoop 객체에서 호출된 runUntilDate: 메소드를 종료시킵니다.

타이머 소스는 핸들러 루틴에 이벤트를 전달하지만 런 루프를 종료시키지는 않습니다.

그림 3-1

입력 소스를 처리하는 것 외에도, 런 루프는 런 루프의 행동에 대한 알림을 생성합니다.

등록된 런 루프 옵저버는 이러한 알림을 받아 스레드에서 추가 처리를 할 수 있습니다. 여러분은 코어 파운데이션(Core Foundation)을 사용하여 여러분의 스레드에 런 루프 옵저버를 설치합니다.

다음 섹션들은 런 루프의 구성 요소와 그들이 작동하는 모드에 대한 자세한 정보를 제공합니다. 또한, 이벤트 처리 동안 다른 시기에 생성되는 알림에 대해서도 설명합니다.

런 루프 모드

런 루프 모드는 모니터링할 입력 소스와 타이머의 집합, 그리고 알림을 받을 런 루프 옵저버의 집합입니다.

런 루프를 실행할 때마다, 명시적으로나 묵시적으로 특정한 “모드”를 지정합니다.

런 루프의 해당 패스 동안에는 그 모드와 관련된 소스만이 모니터링되고 이벤트를 전달할 수 있습니다. (마찬가지로, 해당 모드와 연관된 옵저버만 런 루프의 진행 상황에 대해 알림을 받습니다.) 다른 모드와 연관된 소스는 적절한 모드에서 루프를 통과할 때까지 새 이벤트를 보류합니다.

코드에서는 모드를 이름으로 식별합니다. 코코아(Cocoa)와 코어 파운데이션(Core Foundation) 모두 기본 모드와 여러 흔히 사용되는 모드를 정의하며, 코드에서 이러한 모드를 지정하기 위한 문자열도 제공합니다.

사용자 정의 모드는 모드 이름에 대한 사용자 정의 문자열을 지정함으로써 정의할 수 있습니다. 사용자 정의 모드에 할당하는 이름은 임의적일 수 있지만, 그 모드의 내용은 그렇지 않습니다.

유용하게 만들기 위해서는 하나 이상의 입력 소스, 타이머 또는 런 루프 옵저버를 생성한 모드에 반드시 추가해야 합니다.

모드를 사용하여 런 루프를 통과하는 동안 원치 않는 소스로부터의 이벤트를 필터링합니다.

대부분의 경우, 시스템 정의 Default 모드에서 런 루프를 실행하고 싶을 것입니다. 하지만 모달 패널은 Modal 모드에서 실행될 수 있습니다.

이 모드에서는 모달 패널과 관련된 소스만이 스레드에 이벤트를 전달합니다. 보조 스레드의 경우, 시간에 민감한 작업 중에 저우선순위 소스로부터의 이벤트 전달을 방지하기 위해 사용자 정의 모드를 사용할 수 있습니다.

💡 Note. 모드는 이벤트의 유형이 아니라 이벤트의 출처를 기준으로 구별합니다.

예를 들어, 마우스 다운 이벤트나 키보드 이벤트만을 일치시키기 위해 모드를 사용하지는 않습니다. 서로 다른 포트의 응답을 받거나, 타이머를 일시적으로 중단하거나, 현재 모니터링되고 있는 소스와 런 루프 옵저버를 변경하는 등의 목적으로 모드를 사용할 수 있습니다.

표 3-1은 코코아와 코어 파운데이션에서 정의한 표준 모드와 그 모드를 사용하는 경우에 대한 설명을 나열합니다. 이름 열에는 코드에서 모드를 지정하는 데 사용하는 실제 상수가 나열되어 있습니다.

image

DispatchQueue란?

디스패치 큐(Dispatch queues)는 블록 객체 형태의 작업을 제출할 수 있는 FIFO(선입선출) 큐입니다.

디스패치 큐는 작업을 순차적으로 또는 동시에 실행합니다. 디스패치 큐에 제출된 작업은 시스템에 의해 관리되는 스레드 풀에서 실행됩니다.

앱의 메인 스레드를 나타내는 디스패치 큐를 제외하고, 시스템은 어떤 스레드를 사용하여 작업을 실행할지 보장하지 않습니다.

작업 항목을 동기적으로 또는 비동기적으로 스케줄링할 수 있습니다. 작업 항목을 동기적으로 스케줄링하면, 코드는 해당 항목이 실행을 완료할 때까지 대기합니다.

작업 항목을 비동기적으로 스케줄링하면, 코드는 계속 실행되는 동안 해당 작업 항목이 다른 곳에서 실행됩니다.

⚠️ 메인 큐에서 작업 항목을 동기적으로 실행하려고 시도하면 교착 상태가 발생합니다.

기본적으로 디스패치 큐는 자동 해제(autorelease) 객체에 대해 최소한의 지원을 제공합니다.

시스템 API는 자동 해제 객체를 코드로 반환할 수 있습니다. 예를 들어, NSError 객체는 종종 자동 해제됩니다.

블록 내에서 생성된 자동 해제 객체로 인해 메모리 압박이 증가하는 것을 보게 되면, 해당 블록에 autorelease pool을 추가하여 압박을 완화하는 것을 고려해 볼 수 있습니다.

또한, 디스패치 큐의 기본 자동 해제 동작을 생성 시 에 [dispatch_queue_attr_make_with_autorelease_frequency](https://developer.apple.com/documentation/dispatch/1642197-dispatch_queue_attr_make_with_au) 함수를 사용하여 구성할 수도 있습니다.

DispatchQueue.main()은 ?

위에서 설명했듯이 디스패치 큐는 시스템에 의해 관리되는 스레드 풀입니다.

main dispatch queue는 앱의 main 쓰레드에서 task를 실행하는, 전역적으로 사용 가능한 serial queue입니다.

serial queue는 한 번에 하나의 작업만 수행하는 큐입니다.

runloop와 dispatchqueue의 차이점은?

runloop와 dispatchqueue의 가장 큰 차이점은 runloop의 경우 각각의 모드가 존재한다는 점이 있습니다.

예를 들어 스크롤 중에 이미지를 불러오는 코드를 runloop와 dispatchqueue를 각각 사용하여 구현했다고 가정해보겠습니다.

  1. runloop를 사용한 경우

    위에서 언급한듯이 메인 스레드에는 연결된 runloop 객체가 있습니다. 그리고 스크롤 이벤트를 메인 스레드에서 받습니다. 이때 runloop의 모드는 event tracking 모드 입니다. 하지만 불러온 이미지를 설정하는 코드는 default 모드입니다. 따라서 runloop는 event tracking 모드가 끝날 때 까지 불러온 이미지를 설정하는 것을 보류합니다.

    즉, 스크롤이 끝나야 이미지가 보입니다.

  2. dispatchqueue.main.async를 사용한 경우

    DispatchQueue.main.async를 사용하는 경우, 작업은 메인 스레드의 큐에 추가되지만, 호출 스레드는 작업의 완료를 기다리지 않습니다. 이는 스크롤과 같은 사용자 인터랙션을 방해하지 않으면서도 이미지를 로딩할 수 있게 합니다.

    즉, 스크롤을 하면서도 이미지가 보입니다.

이러한 차이를 인지하고 적절하게 사용하는게 좋겠습니다.


참고자료

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

https://www.avanderlee.com/combine/runloop-main-vs-dispatchqueue-main/

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.