![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
RunLoop은 특정 스레드에서 들어오는 이벤트의 수신 및 처리를 조정하는 루프입니다.
RunLoop은 모든 스레드에 존재하지만 기본적으로 대기 모드에 있으며 어떤 작업도 수행하지 않습니다.
필요한 경우 개발자가 실행할 수 있지만 자동으로 작동하지는 않습니다. 이렇게 하려면 코드를 작성해야 합니다.
우선, RunLoop은 들어오는 작업의 흐름을 관리하고 적시에 실행하도록 설계되었습니다.
이는 UIScrollView를 사용하는 등 UI 작업을 할 때 가장 두드러집니다.
기본적으로 기본 RunLoop은 항상 애플리케이션에서 실행됩니다. 시스템의 메시지를 처리하여 애플리케이션으로 전송합니다. 이러한 메시지의 예로는 사용자가 화면을 클릭할 때 발생하는 이벤트를 들 수 있습니다.
보조 스레드에는 RunLoop의 필요성을 자체적으로 결정해야 합니다. 필요한 경우 직접 구성하고 실행해야 합니다. 기본적으로 RunLoop을 실행하는 것은 권장되지 않으며 스레드와의 활성 상호 작용이 필요한 경우에만 필요합니다.
또한 애플리케이션의 모든 타이머는 런루프에서 실행되므로 애플리케이션에서 타이머와 상호 작용해야 하는 경우 반드시 런루프의 기능을 연구해야 합니다.
RunLoop은 루프이며 개발자가 특정 작업을 실행할 시기를 이해하는 데 도움이 되는 여러 가지 작동 모드를 가지고 있습니다.
따라서 RunLoop은 다음 모드에 있을 수 있습니다:
Default
- 기본 모드로 스트림은 무료이며 대규모 작업을 안전하게 수행할 수 있습니다.
Tracking
- 스레드가 중요한 작업을 수행 중입니다. 이 시점에서는 어떤 작업도 실행하지 않거나 최소한 몇 가지 작은 작업을 실행하는 것이 좋습니다.
Initialization
- 이 모드는 스트림 초기화 중에 한 번 실행됩니다.
EventReceive
- 시스템 이벤트를 수신하기 위한 내부 모드로 일반적으로 사용되지 않습니다.
Common
- 실질적인 의미가 없는 자리 표시자 모드입니다.
메인 RunLoop에서는 이러한 모드가 자동으로 전환됩니다. 개발자는 이를 사용하여 시간이 많이 걸리는 작업을 수행할 수 있으므로 사용자는 인터페이스 중단을 눈치채지 못합니다. 예를 살펴보겠습니다.
다른 RunLoop의 실행 주기 관리는 완전 자동이 아닙니다. 적절한 시간에 실행 주기를 시작하는 스레드에 대한 코드를 작성해야 합니다. 또한 이벤트에 적절하게 응답하고 무한 루프를 사용하여 실행 주기가 멈추지 않도록 해야 합니다.
우리는 UIScrollView를 가지고 있고, 사용자가 아무 것도 눈치채지 못하도록 메인 스레드에서 대규모 작업을 수행해야 합니다.
일반적인 방법으로 작업을 완료할 수 있습니다.
DispatchQueue.main.async { sleep(2) self.tableView.refreshControl?.endRefreshing() }
하지만 결과는 꽤 나쁠 것이다. 사용자는 애플리케이션이 상당히 지연된다는 것을 알게 될 것입니다.
이러한 부정적인 효과는 현재 무슨 일이 일어나고 있는지 전혀 주의를 기울이지 않고 메인 스레드에서 작업을 실행한다는 사실 때문에 발생합니다.
이 때문에 우리는 사용자가 인터페이스와 상호작용하는 순간부터 큰 작업을 수행하기 시작합니다. 물론 이로 인해 사용자는 인터페이스가 정지되는 것을 보게 됩니다.
이는 RunLoop 메커니즘을 사용하여 방지할 수 있습니다. 다음을 사용하여 동일한 논리를 구현해 보겠습니다.
CFRunLoopPerformBlock(CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue) { sleep(2) self.tableView.refreshControl?.endRefreshing() }
여기서 무슨 일이 일어나는지 설명하겠습니다. CFRunLoopPerformBlock 함수는 RunLoop을 통해 실행하기 위한 코드를 추가합니다. 코드 블록 자체 외에도 이 함수에는 2개의 중요한 매개변수가 있습니다.
첫 번째는 어떤 RunLoop이 함수를 실행해야 하는지 선택하는 역할을 담당합니다. 이 예에서는 "main"이 사용되었습니다.
두 번째는 작업이 완료되는 모드를 담당합니다.
총 3가지 가능한 모드가 있습니다:
공통 - 기본 모드와 추적 모드를 결합합니다.
위의 코드로 프로그램을 실행한 결과는 다음과 같습니다.
사용자가 사용자 인터페이스(UI)와 상호 작용하기 시작하면 기본 실행 루프는 "추적" 모드로 전환되고 인터페이스의 원활한 작동을 보장하기 위해 다른 모든 이벤트의 처리를 일시적으로 중단합니다. 사용자가 인터페이스와의 상호작용을 멈추면 런 루프는 "기본" 모드로 돌아가서 작업 수행을 재개합니다.
사용자 인터페이스 외에도 루프는 타이머 기능과도 밀접하게 연결되어 있습니다.
애플리케이션의 모든 타이머는 루프에서 실행되므로 작업하는 동안 실수하지 않도록 각별히 주의해야 합니다. 특히 결제 처리와 같은 중요한 기능을 담당하는 경우 더욱 그렇습니다.
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething }
타이머는 기본적으로 기본 모드에서 시작되므로 사용자가 현재 테이블을 스크롤하는 경우 작동이 중지될 수 있습니다. 이는 루프가 현재 추적 모드에 있기 때문입니다. 이것이 예제의 코드가 제대로 작동하지 않는 이유입니다. 공통 모드에 타이머를 추가하면 이 문제를 해결할 수 있습니다.
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething } RunLoop.main.add(timer, forMode: .common)
또한 타이머가 예상 시간에 작동하지 않거나 전혀 작동하지 않을 수도 있습니다. 이는 RunLoop이 각 사이클이 시작될 때만 타이머를 확인하기 때문입니다. RunLoop이 이 단계를 통과한 후에 타이머가 트리거되면 다음 반복이 시작될 때까지 이에 대해 알 수 없습니다. 동시에 RunLoop에서 작업이 더 오래 실행될수록 지연 시간도 길어집니다.
이 문제를 해결하려면 새 스레드를 생성하고 해당 스레드에서 RunLoop을 시작한 다음 다른 작업을 추가하지 않고 스레드에 타이머를 추가하면 됩니다. 이렇게 하면 타이머가 올바르게 작동합니다.
let thread = Thread { let timer = Timer(timeInterval: 1.0, repeats: true) { timer in // makeSomething } RunLoop.current.add(timer, forMode: .default) RunLoop.current.run() } thread.start()
이 기사에서는 RunLoop이 무엇인지, 그리고 이것이 iOS 애플리케이션에서 어떤 문제를 해결하는지 살펴보았습니다. RunLoop은 특정 스레드 내에서 들어오는 이벤트의 수신 및 처리를 조정하는 루프이며 여러 가지 작동 모드를 가지고 있습니다.
이는 사용자 인터페이스(UI) 및 타이머로 작업할 때 특히 유용합니다. 적시에 작업을 실행하는 기능이 있어 인터페이스 "중단"을 방지하고 타이머의 올바른 작동을 보장할 수 있기 때문입니다.
Run Loop를 사용하려면 추가 코딩이 필요하다는 사실에도 불구하고 애플리케이션의 효율성과 안정성을 향상시키는 것은 가치 있는 투자입니다.