[Apple] Threading Programming Guide: Run Loops

J.Noma·2022년 1월 31일
0

iOS : Cocoa Touch

목록 보기
2/4

runloop
run loops
런루프


Reference


✅ Remind

  • 런루프는 스레드마다 갖고 있으며 스레드와 함께 생성된다
  • 일반적으로 런루프 실행은 명시적으로 해줘야 하나, 메인 런루프는 프레임워크가 해준다

🌀 런루프란?

런루프란 일반적인 코드 실행이 아닌 이벤트 처리 루틴과 타이머 동작 수행을 위해 만들어진 객체입니다. 런루프 객체는 각 스레드마다 가지고 있으며 해당 스레드 생성 시점에 자동으로 함께 생성됩니다. 해당 스레드로 이벤트가 들어오거나 타이머 동작이 요청되면 자신이 가진 런루프를 통해서 이들을 처리하게 됩니다

런루프는 그 이름과는 달리, 알아서 반복하지 않고 코드로 직접 실행해주어야 합니다. 또한 이 런루프 실행코드에 명시해준 기간이 지나면 멈추어 더 이상 처리를 해주지 않습니다. 즉, 어떤 스레드에게 이벤트를 던지거나 타이머 동작을 요청하더라도 이를 수행할 런루프를 실행하지 않으면 처리되지 않습니다 (공식문서에선 런루프의 목적을 "할 일이 있을 때는 스레드를 최대한 바쁘게 만들어 처리하고, 할 일이 없을 때는 sleep하도록 관리하는 것"이라고 말하고 있습니다). 단, 메인 스레드의 메인 런루프는 App 프레임워크가 startup 과정에서 자동으로 실행시켜 계속 반복하므로 개발자인 우리가 제어하지 않아도 됩니다

(런루프 객체에 대한 추가 정보는 NSRunLoop Class Reference, CFRunLoop Reference를 참고)

✅ 이벤트 처리 과정

0. 런루프 대기 중에 Input sources/Timer sources 이벤트들이 발생하고 pending 됨
1. 런루프 실행 시, pending 되어 있던 이벤트들에 대한 정해진 핸들러 메서드들을 호출
2. 핸들러 메서드 완료 후 변경될 필요가 있는 사항 적용 (view의 경우 setNeedsLaytout 등)
3. 런루프를 run(until:)로 실행했다면 지정한 시간까지 런루프를 반복하며 이벤트 수신/처리를 수행. 할일 없으면 suspend


🌀 이벤트 소스

1. Input Sources

마우스/키보드 이벤트처럼 다른 스레드나 App에서 비동기적으로 전달되는 메시지 이벤트를 말합니다. input sources는 대응되는 핸들러에게 이벤트를 전달하고 runUntilDate: 메서드를 통해 런루프 종료를 시도합니다 (runUntilDate는 해당 스레드의 런루프 객체가 호출)

input source은 보통 2가지 카테고리 중 하나입니다. Port-based input source는 App의 Mach port를 감시합니다. Custom input source는 커스텀 이벤트 소스를 감시합니다

두 input source의 유일한 차이점은 어떻게 시그널을 보내느냐입니다. Port-based source는 OS 커널에 의해 자동으로 시그널이 전달되는 반면 Custom source는 다른 스레드 상에서 수동으로 시그널을 보내야 합니다

2. Timer Sources

미래의 지정된 시간(예약된 시간, 반복간격)에 동기적으로 전달되는 이벤트를 말합니다. timer source는 대응되는 핸들러 루틴에게 이벤트를 전달하지만 런루프 종료를 야기하진 않습니다


🌀 런루프 모드

런루프 모드는 현재 런루프가 어떤 종류의 이벤트 소스를 받게할지어떤 런루프 옵저버를 동작시킬지의 집합입니다. 런루프를 실행할 때마다 어떤 모드로 실행할지를 정하게 되고, 이에 따라 실행된 런루프로는 해당하는 이벤트 소스만 이벤트를 보낼 수 있고 해당하는 옵저버만 알림을 받을 수 있습니다. 그 사이, 해당하지 않는 이벤트 소스들이 들어오면 적절한 모드로 런루프가 실행될 때까지 홀드됩니다. 기본적으로 Cocoa/CoreFoundation에서 제공하는 모드들이 있고 custom으로 만들 수도 있습니다

  • Default
    default 모드는 가장 대중적으로 사용되는 기본 모드입니다

  • Connection
    Cocoa uses this mode in conjunction with NSConnection objects to monitor replies. 이 모드를 직접 사용할 일은 거의 없다

  • Modal
    Modal panel에 대한 이벤트를 식별할 때

  • Event Tracking
    마우스 드래그와 같은 UI tracking 타입의 루프가 도는 동안 다른 이벤트가 들어오는 것을 제한하고 싶을 때

  • Common Mode
    일반적으로 사용되는 여러 모드를 묶은 것. CFRunLoopAddCommonMode 함수를 통해 모드를 추가할 수 있으며, common 모드로 실행된 런루프는 안에 포함된 모든 모드에 속하게 된다. 기본값으로 Cocoa는 default+modal+eventTracking이고, CoreFoundation은 default이다


🌀 런루프 옵저버

이벤트 발생 시점에 동작하는 이벤트 소스와는 달리, 런루프 옵저버는 런루프 자체의 실행 중 특정 시점에 동작합니다. 주어진 이벤트를 처리할 스레드를 준비시키거나 스레드를 sleep시킬 준비를 하는데 옵저버를 사용할 수 있습니다

옵저버를 아래에 나열한 시점들에 연결할 수 있습니다

  • 런루프 시작 시점
  • 타이머 이벤트 처리 직전
  • input source 이벤트 처리 직전
  • 런루프 sleep 직전
  • 런루프가 깨어났지만, 아직 자신을 깨운 이벤트를 처리하기 전
  • 런루프 종료 시점

🌀 런루프 이벤트 처리과정 요약

(아래 순서는 macOS 오픈소스 코드를 기준으로 하며, iOS의 경우 다른 결과가 나온다는 말이 있고 실제 코드가 비공개이므로 충분한 실험이 필요합니다)

런루프가 실행될 때마다, 스레드의 런루프는 pending 되어 있던 이벤트들을 처리하고 부착된 옵저버로 notification을 만들어 보냅니다

  1. 옵저버에게 런루프 시작을 알림
  2. 옵저버에게 타이머들이 fire할 준비가 되었음을 알림
  3. 옵저버에게 Port-based가 아닌 input source들이 동작할 준비가 되었음을 알림
  4. Port-based가 아닌 input source를 동작시킴
  5. Port-based input source가 준비되었다면 이를 즉시 처리하고 9번으로 이동
  6. 옵저버에게 스레드 sleep 직전임을 알림
  7. 아래 이벤트 중 하나가 발생할 때까지 스레드를 재움
    • Port-based input source 이벤트가 도착
    • 타이머 fire
    • 런루프에 설정된 타임아웃이 만료됨
    • 런루프가 명시적으로 깨워짐
  8. 옵저버에게 스레드가 방금 일어났음을 알림
  9. pending 되어 있던 이벤트를 처리
    • 유저가 정의한 타이머가 fire 되었다면, 타이머 이벤트를 처리하고 루프를 재시작. 2번으로 이동
    • input source가 fire 되었다면, 이벤트를 핸들러에게 전달
    • 타임아웃이 아닌데 런루프가 명시적으로 깨워진 것이라면, 루프를 재시작. 2번으로 이동
  10. 옵저버에게 런루프 종료를 알림

🌀 어떤 상황에 런루프를 사용해야 할까?

사실 런루프를 명시적으로 실행시키는 경우는 secondary 스레드를 생성할 때 뿐입니다. 이미 언급했듯이, 메인 스레드의 메인 런루프는 App의 중요한 기반이기에 프레임워크가 자동으로 실행시킵니다

그래서 secondary 스레드를 생성하는 경우에서 어떤 상황에 런루프 사용을 고려해야 할까요?

  • input source를 사용하여 다른 스레드들과 소통해야 하는 경우
  • secondary 스레드에서 타이머를 사용하는 경우
  • performSelector... 메서드를 사용하는 경우
  • 주기적인 task를 수행하기 위해 스레드를 유지해야 하는 경우
profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글