관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] Run Loops (런 루프, Thread 프로그래밍, global queue에서 Timer 동작 방법) 본문

iOS 응용 (swift)

[iOS - swift] Run Loops (런 루프, Thread 프로그래밍, global queue에서 Timer 동작 방법)

jake-kim 2021. 6. 3. 23:15

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

  • 특정 이벤트가 왔을 때 쓰레드가 일해야 할 때는 일하고, 일이 없으면 쉬도록하기 위해 애플에서 만든 쓰레드관리 Loop
  • 이벤트가 들어오면 이벤트 헨들러를 실행하는데, 이벤트 핸들러를 언제 실행하지 결정해주는 루프를 의미

좌측: Thread는 노란색 RunLoop를 가지고 있고, 한바퀴 돌면서 Event들을 모아놨다가, 한꺼번에 처리

  • Run Loop는 두 가지의 소스를 수신
    • 1) input source: 한 루프 한바퀴를 도는 동안, 다른 스레드나 다른 응용프로그램의 비동기 이벤트가 수신된 것을 확인하고, 이벤트 핸들러 수행
    • 2) timer source: 한 루프 한바퀴를 도는 동안, 예정된 시간이나 반복되는 간격으로 발생하는 동기 이벤트를 수신된 것을 확인하고, 이벤트 핸들러 수행
  • Thread를 생성하고 Thread.current에 접근할 때 Run Loop가 없다면 생성하고, 있으면 기존에 있으면 그것을 사용
    • Run Loop의 실행은 개발자가 직접 실행
    • 특정 Thread가 input source나 timer source를 처리해야 하는 경우, RunLoop에 직접 접근하여 실행해야 가능

Run Loops와 Thread

  • Thread는 모두 각자의 Run Loop를 소유
  • Thread를 생성하고 Thread.current에 접근할 때 Run Loop가 없다면 생성
  • 주의: Run Loop는 자동으로 실행되지 않는 형태 -> Run Loop 실행하기 위해서는 프로그래머가 직접 호출
  •  

Main과 Global Thread에서의 Run Loop 

  • Main thread에서는 Run Loop가 자동으로 설정되고 실행 (Main Run Loop)
  • Global thread에서는 Run Loop를 프로그래머가 직접 실행해야 동작

cf) Main queue vs Global queue

  main queue global queue
갯수 한 개 여러 개
  serial concurrent
스레드 main thread QoS에 따른 queue 생성
  DispatchQueue.main.async{} DispachQueue.global()
DispachQueue.global(qos:)
  • 즉 GCD(Global Central Dispatch)를 통해 Global thread를 사용할 때 Run Loop를 프로그래머가 실행시켜주지 않으면 동작되지 않는 경우가 존재
    • ex) Timer 설정, Socket Input, touch input 등
// Timer 동작 안하는 경우가 존재

// global thread - 동작 x
DispatchQueue.global().async {
    Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
        print("타이머 동작!")
    }
}

// Main Thread - 동작 o
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
    print("타이머 동작!")
}

Run Loop 실행 방법

  • Run Loop 객체 참조 방법: 생성된 queue안에서 객체를 획득하여 사용 (RunLoop는 자동으로 생성되므로 참조하여 바로 사용)

let runLoop = RunLoop.current
  • 실행 방법 - 4가지 메서드로 실행

  • run(): Input sources, Timer sources들을 영구적으로 처리
DispatchQueue.global().async {
    Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
        print("타이머동작!")
    }
    RunLoop.current.run()
}

* 주의) Timer Sources 실행 전 run()을 호출하면 동작하지 않는것을 주의: 이벤트가 발생한 후 run()을 통해 loop로 받아야 하므로

// 동작 x

DispatchQueue.global().async {
    RunLoop.current.run()
    Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
        print("타이머동작!")
    }
}
  • run(until:): 지정된 date까지 loop실행, 이 메소드를 가장 많이 사용
    • run()은 영구적으로 실행되므로 기한을 정해주어 자원 낭비를 줄일 수 있는 run(until:) 지향

ex) 간단한 예제 - isRunning을 false로 해주면 멈추게끔

* 만약 sync하게 동작할 경우 while문에서 무한 loop가 진행되므로, 실제로 구현 시 running 작업을 또 다른 thread로 구분하여 설계

DispatchQueue.global().async {
    let isRunning = true
    let runLoop = RunLoop.current
    Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
        print("타이머동작!")
    }

    while isRunning {
        runLoop.run(until: Date().addingTimeInterval(0.1))
    }
}

ex) socket 통신 - run loop이 sync에서 짜여있어도 최종적으로 async에서 work를 부르는 형태로 동작

    var workItemRunLoop: DispatchWorkItem?
    func runLoopSample() {
        workItemRunLoop = DispatchWorkItem {
            let runLoop = RunLoop.current
            let inputStream = InputStream()
            let outputStream = OutputStream()

            inputStream.schedule(in: runLoop, forMode: .default)
            outputStream.schedule(in: runLoop, forMode: .default)

            while !(self.workItemRunLoop?.isCancelled ?? true) {
                runLoop.run(until: Date().addingTimeInterval(0.1))
            }
        }
        guard let workItemRunLoop = workItemRunLoop else { return }
        DispatchQueue.global().async(execute: workItemRunLoop)
    }

Mode를 필요로 하는 run

  • run(mope:before:): loop를 한번 실행하며, 지정된 mode와 date까지 input을 blocking
  • acceptInput(forMode:before:): loop를 한 번 실행하며, 지정된 모드에서만 입력을 허용
  • mode의 개념: Input sources

모드 이름 설명
Default RunLoop.Mode.default NSConnection객체르 제외한 input sources 처리 모드
Tracking RunLoop.Mode.tracking 컨트롤을 추적하고 있는 상황에서 사용
Common RunLoop.Mode.common -일반적으로 사용되는 모드에 같이 사용가능한 그룹
-Input Source를 이 모드와 연결하여 각 모드와 연결하여 사용

RunLoop 사용처

  • global queue에서 Input Source를 통해서 다른 thread와 통신
  • global queue에서 Timer 사용
  • global queue에서 주기적인 일을 계속 수행하는 경우

* 참고

https://developer.apple.com/documentation/foundation/runloop/run_loop_modes

https://developer.apple.com/documentation/foundation/runloop

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

https://babbab2.tistory.com/68

Comments