runloop
run loops
런루프
Reference
- 내용전반: Apple Archive
- RunLoop: Apple문서
- Timer: Apple문서
- 내용전반: 소들이님블로그
- 내용전반: Rhyno's DevLife
- 런루프처리과정: Inboon's Story
✅ Remind
- 런루프는 스레드마다 갖고 있으며 스레드와 함께 생성된다
- 일반적으로 런루프 실행은 명시적으로 해줘야 하나, 메인 런루프는 프레임워크가 해준다
런루프란 일반적인 코드 실행이 아닌 이벤트 처리 루틴과 타이머 동작 수행을 위해 만들어진 객체입니다. 런루프 객체는 각 스레드마다 가지고 있으며 해당 스레드 생성 시점에 자동으로 함께 생성됩니다. 해당 스레드로 이벤트가 들어오거나 타이머 동작이 요청되면 자신이 가진 런루프를 통해서 이들을 처리하게 됩니다
런루프는 그 이름과는 달리, 알아서 반복하지 않고 코드로 직접 실행해주어야 합니다. 또한 이 런루프 실행코드에 명시해준 기간이 지나면 멈추어 더 이상 처리를 해주지 않습니다. 즉, 어떤 스레드에게 이벤트를 던지거나 타이머 동작을 요청하더라도 이를 수행할 런루프를 실행하지 않으면 처리되지 않습니다 (공식문서에선 런루프의 목적을 "할 일이 있을 때는 스레드를 최대한 바쁘게 만들어 처리하고, 할 일이 없을 때는 sleep하도록 관리하는 것"이라고 말하고 있습니다). 단, 메인 스레드의 메인 런루프는 App 프레임워크가 startup 과정에서 자동으로 실행시켜 계속 반복하므로 개발자인 우리가 제어하지 않아도 됩니다
(런루프 객체에 대한 추가 정보는 NSRunLoop Class Reference, CFRunLoop Reference를 참고)
✅ 이벤트 처리 과정
0. 런루프 대기 중에 Input sources/Timer sources 이벤트들이 발생하고 pending 됨
1. 런루프 실행 시, pending 되어 있던 이벤트들에 대한 정해진 핸들러 메서드들을 호출
2. 핸들러 메서드 완료 후 변경될 필요가 있는 사항 적용 (view의 경우 setNeedsLaytout 등)
3. 런루프를 run(until:)로 실행했다면 지정한 시간까지 런루프를 반복하며 이벤트 수신/처리를 수행. 할일 없으면 suspend
마우스/키보드 이벤트처럼 다른 스레드나 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는 다른 스레드 상에서 수동으로 시그널을 보내야 합니다
미래의 지정된 시간(예약된 시간, 반복간격)에 동기적으로 전달되는 이벤트를 말합니다. 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시킬 준비를 하는데 옵저버를 사용할 수 있습니다
옵저버를 아래에 나열한 시점들에 연결할 수 있습니다
(아래 순서는 macOS 오픈소스 코드를 기준으로 하며, iOS의 경우 다른 결과가 나온다는 말이 있고 실제 코드가 비공개이므로 충분한 실험이 필요합니다)
런루프가 실행될 때마다, 스레드의 런루프는 pending 되어 있던 이벤트들을 처리하고 부착된 옵저버로 notification을 만들어 보냅니다
사실 런루프를 명시적으로 실행시키는 경우는 secondary 스레드를 생성할 때 뿐입니다. 이미 언급했듯이, 메인 스레드의 메인 런루프는 App의 중요한 기반이기에 프레임워크가 자동으로 실행시킵니다
그래서 secondary 스레드를 생성하는 경우에서 어떤 상황에 런루프 사용을 고려해야 할까요?