티스토리 뷰

안녕하세요

오늘은 Swift의 RunLoop에 대해 알아보려고 합니다.

iOS 개발을 하다보면 Timer를 다루는 경우가 자주 생기는데 Timer을 제대로 구현하기 위해서는 RunLoop에 대해 제대로 이해할 필요가 있습니다.

시작하겠습니다.

 


✅ 1. RunLoop란?


iOS 앱은 실행되는 동안 계속 무언가를 기다리는 루프가 돌고 있습니다. 이게 바로 Runloop로, 간단히 말해 스레드와 관련된 핵심 인프라로 이벤트 처리 이후에도 앱이 종료되지 않도록 해주는 이벤트 루프입니다.


RunLoop는 할 일이 있을 때 스레드를 바쁘게 유지하며 할일이 없을 때 스레드를 슬립 상태로 유지하며 이 상태일 때는 CPU를 사용하지 않습니다.

 

우린 Timer를 사용하고자 할때 이런 Runloop에 작업을 등록합니다.

이때 문제가 발생할 수 있는데, 바로 Timer를 등록해놓은 Runloop에 많은 작업이 예약되어 있는 경우 Timer의 동작이 후순위로 밀릴 수 있다는 것 입니다.

그리고 고려해야할 문제는 하나 더 있습니다.

 

RunLoop에는 DefaultTracking 그리고 Common 의 총 3개의 Mode 가 있습니다

Timer를 Default 모드의 Runloop에 등록하는 경우 앱을 스크롤하는 동안은 Timer가 정상적으로 동작하지 않으며 이를 방지하기 위해 우리는 Common 모드의 Runloop에 Timer를 등록해줄 필요가 있습니다.

이러한 이유로 러닝 앱 등 시간을 다루는 앱에서 Timer를 사용하여 시간 초를 재는 방법은 위험이 따릅니다.

따라서 주로 Date()를 사용하여 실제 시간과의 차이를 계산하는 방법이 더 선호됩니다.

 

다시 Runloop로 돌아와 이야기를 이어나가면, Runloop는 메인스레드에서는 자동 시작되지만 그 외의 스레드에서는 수동으로 설정 후 시작해주어야 합니다.

// 백그라운드 스레드에서 RunLoop 직접 돌리기
Thread.detachNewThread {
    // 여기서 Timer 등록하고 싶으면?
    let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
        print("백그라운드 타이머")
    }
    RunLoop.current.add(timer, forMode: .default)
    RunLoop.current.run() // 수동으로 시작해야 함
}

 

✅ Runloop의 이벤트 소스 종류

Runloop의 이벤트 소스는 Input Source와 Timer Source 두 종류로 나뉘어져 있습니다.

 

Input Source는 비동기 데이터를 전달하며 Runloop를 종료시킬 수 있습니다.

반대로 Timer Source는 동기 이벤트를 전달합니다. 그리고 핸들러에 이벤트를 전달하지만 Runloop를 종료시킬 수 없습니다.

Timer을 설정할 때 주의해야할 점이 있습니다.

 

Timer은 실시간 메커니즘이 아니므로 Timer을 실행시키면 이미 진행중인 Runloop의 루틴이 전부 완료된 이후에 호출되게 됩니다. 따라서 어느정도의 딜레이가 발생할 수 있으며 실행되고 있는 Runloop가 없는 경우에는 Timer은 실행되지 않습니다.

 

✅ Timer 설정하기

Scheduler에 Timer을 설정하는 방법은 아래와 같습니다.

// 클로저 방식
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("1초마다 실행")
}

// selector 방식 (구식)
Timer.scheduledTimer(timeInterval: 1.0, 
                     target: self, 
                     selector: #selector(tick), 
                     userInfo: nil, 
                     repeats: true)

 

Timer은 참고로 타겟을 강한참조 하여야 하기 때문에 클로저 내부에서는 weak self 처리를 해줄 필요가 있습니다.

 

이렇게 설정하는 경우 아까 언급한 Mode의 문제가 생깁니다. 

 

따라서 이 문제를 피하기 위해서는 아래와 같이 Mode를 직접 설정하여 등록해줄 필요가 있습니다.

 

let timer = Timer(timeInterval: 1.0, repeats: true) { timer in
    print("실행")
}

// 직접 RunLoop에 등록 — .common으로 하면 스크롤 중에도 동작
RunLoop.main.add(timer, forMode: .common)

 

반대로 Runloop에서 예약을 취소하기 위해서는 아래와 같이 incalidate를 해주면 되며 한번 invalidate된 Timer은 재사용이 불가능합니다.

timer?.invalidate()

 

 

✅ Runloop vs GCD vs Async/Await

셋의 관계를 정리해보면 아래와 같습니다

방식 기반 특징 주로 쓰는 경우
RunLoop 단일 스레드 이벤트 루프 Timer, 이벤트 소스 처리 Timer, 입력 이벤트
GCD 스레드풀 비동기 작업 분산 네트워크, 파일 IO
async/await GCD 위에서 동작 가독성 좋은 비동기 현대적인 비동기 코드

 

GCD와 async/await는 RunLoop를 직접 몰라도 쓸 수 있게 추상화된 방식입니다. 하지만 Timer를 다루거나, UIKit 이벤트 처리 흐름을 이해하려면 RunLoop 개념이 바탕이 됩니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/08   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
글 보관함