swift

Run Loop

kimyounggyun 2022. 11. 21. 04:19

Run Loop란

런 루프(Run loop)는 스레드에 전달된 소켓, 파일, 키보드, 마우스 등의 입력과 타이머 객체를 처리하는 객체이다. 런 루프를 사용해 스레드의 활동 상태를 조정할 수 있으며 이것이 런 루프가 고안된 이유이다. 모든 스레드는 생성될 때 자신만의 런 루프를 갖는다. 메인 스레드에 생성된 메인 런 루프는 생성과 동시에 자동으로 실행(Start)되지만 그 외 스레드의 런 루프는 자동으로 실행되지 않는다. 따라서 개발자는 적절한 타이밍에 직접 런 루프를 실행시켜 이벤트를 처리해야 한다. 

입력 소스와 런 루프의 구조

입력 이벤트의 종류

런 루프는 2가지 입력 이벤트를 받는다. Input source는 다른 스레드나 애플리케이션에서 비동기적으로 전달된 이벤트이다. Timer source는 예정된 시간 또는 반복된 Interval로 발생되는 동기적으로 전달된 이벤트이다. 입력 이벤트를 처리하기 위해 스레드의 리시버는 런 루프 객체에 접근하여 입력 이벤트를 확인하고 그에 대응되는 이벤트 핸들러(application-specific handler routine)를 사용한다. 런 루프는 자체적으로 반복적으로 실행되지 않는다. 하나의 입력 이벤트를 처리하면 대기 상태로 바뀌기 때문에 그다음 입력 이벤트가 오면 처리되지 않는다. 따라서 일정 시간 동안 들어온 입력 이벤트 처리하기 위해 개발자는 직접 for-loop과 같은 반복문을 사용하여 런 루프를 지속적으로 실행시켜야 한다.

런 루프의 프로퍼티와 메서드

open class RunLoop : NSObject {
    // Accessing Run Loops and Modes
    open class var current: RunLoop { get } 
    open class var main: RunLoop { get }
    open var currentMode: RunLoop.Mode? { get }

    // Running a Loop
    open func run()
    open func run(mode: RunLoop.Mode, before limitDate: Date) -> Bool
    open func run(until limitDate: Date)
    open func acceptInput(forMode mode: RunLoop.Mode, before limitDate: Date)
    
    // Managing Timers
    open func add(_ timer: Timer, forMode mode: RunLoop.Mode)

    // Managing Ports
    open func add(_ aPort: Port, forMode mode: RunLoop.Mode)
    open func remove(_ aPort: Port, forMode mode: RunLoop.Mode) 
}

런 루프에 접근하는 방법

  • class var current RunLoop :  현재 스레드에 런 루프를 반환한다.
  • class var main: RunLoop : 메인 스레드에 런 루프를 반환한다.
  • var currentMode: RunLoop.Mode? : 현재 리시버의 지정된 모드를 반환한다. (acceptInput, run 메서드를 통해 현재 모드 설정 가능)

런 루프를 실행하는 방법

런 루프를 다른 스레드에서 사용하기 위해서는 최소한 한 개의 입력 소스나 타이머가 입력되어야 한다. 만약 어떠한 입력 이벤트가 없다면 런 루프를 실행시 즉시 종료된다.

func run()

Puts the receiver into a permanent loop, during which time it processes data from all attached input sources.

기본 run 메서드는 해당 스레드 리시버를 현재 모드에 입력된 모든 입력 이벤트들을 처리하는 무한 루프에 넣는다. 런 루프에 입력된  입력 소스나 타이머가 있는 경우 이 메서드는 default 모드에서 run(mode: before: )를 무한히 호출한다. 런 루프에 입력된 모든 입력 소스나 타이머를 제거한 다고 해도 런 루프가 종료된다는 보장은 없다. 런 루프가 유한히 반복되는 것을 원한다면 아래의 다른 메서드를 직접 반복 호출해야 한다.

func run(mode: RunLoop.Mode, before limitDate: Date) -> Bool 

Runs the loop once, blocking for input in the specified mode until a given date.

런 루프에 입력된 입력 소스나 타이머가 없는 경우 이 메서드는 즉시 종료되고 false를 반환한다. 그렇지 않으면 첫 번째 입력 소스(타이머는 입력 소스가 아니다.)가 처리되거나 limitDate에 도달한 후에 true를 반환한다. run() 메서드와 마찬가지로 런 루프에 연결된 입력 소스나 타이머를 제거한다고 해도 런 루프가 즉시 종료된다는 보장은 없다. 

func acceptInput(forMode mode: RunLoop.Mode, before limitDate: Date)

Runs the loop once or until the specified date, accepting input only for the specified mode.

런 루프에 입력된 입력 소스나 타이머가 없는 경우 이 메서드는 즉시 종료된다. 그렇지 않으면 루프를 한번 또는 지정된 날짜까지 실행하고 지정된 모드에 대한 입력만 허용한다.

func run(until limitDate: Date)

Runs the loop until the specified date, during which time it processes data from all attached input sources.

런 루프에 입력된 입력 소스나 타이머가 없는 경우 이 메서드는 즉시 종료된다. 그렇지 않으면 limitDate까지 default 모드에서 run(mode: before: )를 반복적으로 호출한다. 이 메서드도 런 루프에 연결된 입력 소스나 타이머를 제거한다고 해도 런 루프가 즉시 종료된다는 보장은 없다. 런 루프를 종료하기 위해선 임의의 조건을 추가하는 방법이 있다. 

var shouldKeepRunning: Bool = true // global
while shouldKeepRunning {
    runLoop.run(until: Date().addingTimeInterval(0.1))
}

언제 사용하는가?

런 루프를 사용하는 경우는 직접 스레드를 정의하여 사용하는 경우이다. 메인 스레드의 런 루프는 애플리케이션이 실행되어 액티브 상태가 되면 자동으로 실행되기 때문이다. 

  • Input source를 사용해 다른 스레드에서 이벤트를 전달하는 경우
  • timer를 사용하는 경우
  • performSelector 메서드를 사용하는 경우
  • 주기적인 작업을 수행하는 경우

참고

공식 문서의 objective-c 예제를 이해하고 계속해서 업데이트 할 예정!