-
소켓, 파일, 키보드 마우스 등의 입력 소스를 처리하는 이벤트 처리 루프
-
쓰레드가 일해야 할 때는 일하고, 일이 없으면 쉬도록 하는 목적으로 고안
-
Thread의 외부 입력 소스 및 Timer를 처리할 때 등 사용됨
-
Thread는 모두 각자의 RunLoop를 가질 수 있고, Thread를 생성할 때 RunLoop가 자동으로 생성 됨. 하지만 자동으로 실행되진 않음
-
Main RunLoop를 제외하고 RunLoop 실행에 대한 관리는 오롯이 "프로그래머"의 몫
-
따라서 만약 내가 Thread를 생성 했는데, 이 Thread가 입력 소스나 Timer를 처리해야 한다면, RunLoop를 직접 얻어서 실행시켜 주어야 함
-
다음과 같이 현재 실행중인 스레드에 대한 런루프를 얻을 수 있음
let runLoop = RunLoop.current
-
RunLoop를 얻는 것만으로는, 입력 소스 및 타이머를 처리해주진 않기 때문에 run이라는 것을 통해 RunLoop를 직접 실행 시켜주어야 함
-
즉, 앱 구동간에 생성되는 Thread와 함께 생성되어 해당 Thread가 수행해야 할 이벤트들을 핸들링하여 적절한 수행을 요청하고, 이벤트 처리가 종료되면 대기 상태로 접어들어 다음 이벤트에 대한 모니터링을 해주는 추상화
- RunLoop는 루프를 수행할 때, 총 2가지 Event Source를 수신
- Input Source - 다른 Thread나 Application으로부터 온 비동기 이벤트를 전달
- Timer - 예약 된 시간 또는 반복 간격으로 발생하는 동기 이벤트를 전달
-
왼쪽 그림에서 노란 색 루프를 한바퀴 도는 작업이 한 번의 실행이라고 생각 했을 때, RunLoop는 한 번의 실행 동안 내 쓰레드에 도착한 이벤트를 받고, 이에 대한 핸들러를 수행하는 객체
-
루프라고 해서, RunLoop가 자체적으로 계속 이벤트가 들어오나 안들어오나 실행을 반복 한다고 생각할 수 있겠지만, RunLoop는 내부적으로 반복 실행을 하지 않음. 한 번 Event Source를 읽고 전달하는 실행이 끝나면, 그대로 대기
-
그 다음 Event Source가 들어와도 RunLoop는 대기 상태이기 때문에, Event를 받을 수 없음. 따라서, 쓰레드 내에서 프로그래머가 명시적으로 for, while 등을 이용해 RunLoop를 반복 실행시켜 주어야 함
DispatchQueue.global.async {
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in print("")}
}
- 해당 코드가 동작하지 않는 이유는 Global Thread를 손수 생성 했고, 내가 생성한 Global Thread의 RunLoop는 실행되고 있지 않기 때문에Timer를 작동 시켰지만, 내 Thread의 RunLoop가 이 Event를 처리하지 못해서
- Main Thread도 내가 RunLoop 실행시켜준 적 없는데, 왜 Timer가 정상 동작 하는 거임? 에 대한 답은 Main Thread는 애플리케이션이 실행될 때 프레임워크 차원에서 자동으로 RunLoop를 설정하고 실행 하기 때문 -> 이를 Main RunLoop라고 함
-
Input Source는 이벤트를 비동기적으로 스레드에 전달
-
Input Source는 두가지 타입으로 나눌 수 있다
- 애플리케이션의 Mach Port를 감시
- Mach Port는 커널 레벨의 기술이기 때문에, 커널에서 자동으로 이벤트 신호를 발생
- 사용자가 지정한 소스의 이벤트를 감시
- 다른 스레드에서 수동으로 이벤트 신호를 발생시킴
- 이를 만들기 위해서는 CFRunLoopSource 의 관련함수들을 사용하는데, Core Foundation은 이 함수들을 통해 받은 콜백 메소드를 통해 input source를 설정하고, 이벤트를 처리하고, input source를 제거
- 이벤트 처리 로직 뿐아니라, 이벤트 전달 매커니즘도 정의해야 하는데, 이 매커니즘은 다른 스레드에서 동작하면서 input source에 데이터와 이 데이터가 준비되었음을 알리는 역할
- Cocoa에서는 임의의 셀렉터를 어떠한 스레드에서도 실행할 수 있도록 해주는 Custom Input Source를 NSObject 객체를 통해 제공. 이를 Perform Selector Source라고 함
- Timer Source는 지정된 시간에 동기적으로 이벤트를 전달
- 스레드에게 무언가를 하도록 알림을 주는 방법 중 하나
-
해당 코드가 동작하지 않는 이유는 Global Thread를 손수 생성 했고, 내가 생성한 Global Thread의 RunLoop는 실행되고 있지 않기 때문에Timer를 작동 시켰지만, 내 Thread의 RunLoop가 이 Event를 처리하지 못해서
-
Main Thread도 내가 RunLoop 실행시켜준 적 없는데, 왜 Timer가 정상 동작 하는 거임? 에 대한 답은 Main Thread는 애플리케이션이 실행될 때 프레임워크 차원에서 자동으로 RunLoop를 설정하고 실행 하기 때문 -> 이를 Main RunLoop 라고 함
-
Loop를 Running 시키는 방법엔 4가지 메서드 존재
-
receiver를 영구 루프(parmanent loop)에 넣고, 이 기간 동안 모든 부착된 Input Source의 데이터를 처리한다
-
아마 run()을 실행할 당시 부착되어있는 Input Source나 Timer는 영구적으로 처리한다는 의미인듯
DispatchQueue.global.async {
let runLoop = RunLoop.current
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in print("1")} //실행
reunLoop.run()
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in print("2")} //미실행
}
- 루프를 한 번 실행하여, 지정된 모드에서 지정된 날짜까지 input을 blocking 한다
-
지정된 날짜까지 루프를 실행하고, 그 기간 동안 루프는 부착된 모든 Input Source들의 데이터를 처리한다
-
보통, RunLoop를 반복 실행할 때 이 메서드를 사용
-
이전에 부착된 Input Source들만 영구적으로 실행되는 run()과 달리,이 메서드는 지정된 날짜까지 루프를 실행하기 때문에, 직접 루프의 실행 시간을 지정해줄 수 있음
-
그리고 그 실행 시간 동안 부착된, 모든 Input Source를 처리해줌
-
보통 0.1 ~ 1초 정도 RunLoop를 실행시키고, 이를 원할 때까지 for, while 문으로 반복하는 형태로 작성
while isRunning { runLoop.run(until: Date().addingTimeInterval(0.1))}
- 만약, 더이상 RunLoop가 필요 없어지면 어디선가 isRunning을 false로 해주면 RunLoop가 더이상 작동하지 않음
- 지정된 날짜까지 또는 지정된 모드에 대해서만 입력을 허용하여 루프를 한 번 실행한다
- RunLoop 모드는 모니터링되는 이벤트 소스와, 알림을 받는 RunLoop 옵저버의 집합
- RunLoop를 실행할 때 모드를 지정하면, 이 모드에 해당하는 이벤트 소스만 이벤트를 보낼 수 있다. 마찬가지로 옵저버도 모드에 해당하는 옵저버만 알림을 받을 수 있다.
- RunLoop모드는 이름(문자열)으로 서로 구분되며, Cocoa와 CoreFoundation은 자주 쓰이는 모드들에 대한 프리셋을 제공
- Default - 대부분의 연산에 대한 기본 모드
- Modal - Modal Panel에서 발생한 이벤트만 처리. macOS에서만 사용 가능
- Event Tracking - 특정 이벤트 루프 중에 다른 이벤트 발생을 막아야 할 때 사용 (마우스 드래깅 이벤트 등). macOS에서만 사용.
- Tracking - 컨트롤을 추적하고 있는 상황에서 사용. iOS,tvOS에서 사용
- Common modes - 여러 Mode의 집합.
- 모드를 이용하면 특정 작업을 할 때 원하지 않는 소스에서 발생한 이벤트 필터링 가능.
- 모드는 이벤트가 발생하는 소스를 기준으로 구별하기 위함이지, 이벤트의 타입을 구별하는 용도가 아님. 예를 들어 전체 마우스 이벤트 중에서 마우스 클릭 이벤트만 필터링 한다던지 하는 기능은 모드만으로는 구현 불가
-
RunLoop를 직접 사용하는 경우는, Thread를 직접 만들어서 사용하는 경우 뿐.
-
메인 스레드의 RunLoop는 앱의 중요한 기반이기 때문에, 애플리케이션 프레임워크가 직접 시동 단계에서 RunLoop를 구성하고 자동으로 시작. 절대로 메인 스레드의 RunLoop 시동 루틴을 직접 호출해서는 안됨.
-
다음과 같은 경우에 RunLoop 필요
-
Input Source를 통해 다른 Thread와 통신하는 경우
-
Timer를 사용해야 하는 경우
-
Perform Selector Source를 사용해야 하는 경우
-
주기적인 일을 계속 수행해야 하는 경우
-