티스토리 뷰
안녕하세요
오늘은 Swift의 RunLoop에 대해 알아보려고 합니다.
iOS 개발을 하다보면 Timer를 다루는 경우가 자주 생기는데 Timer을 제대로 구현하기 위해서는 RunLoop에 대해 제대로 이해할 필요가 있습니다.
시작하겠습니다.
✅ 1. RunLoop란?
iOS 앱은 실행되는 동안 계속 무언가를 기다리는 루프가 돌고 있습니다. 이게 바로 Runloop로, 간단히 말해 스레드와 관련된 핵심 인프라로 이벤트 처리 이후에도 앱이 종료되지 않도록 해주는 이벤트 루프입니다.
RunLoop는 할 일이 있을 때 스레드를 바쁘게 유지하며 할일이 없을 때 스레드를 슬립 상태로 유지하며 이 상태일 때는 CPU를 사용하지 않습니다.
우린 Timer를 사용하고자 할때 이런 Runloop에 작업을 등록합니다.
이때 문제가 발생할 수 있는데, 바로 Timer를 등록해놓은 Runloop에 많은 작업이 예약되어 있는 경우 Timer의 동작이 후순위로 밀릴 수 있다는 것 입니다.
그리고 고려해야할 문제는 하나 더 있습니다.
RunLoop에는 Default와 Tracking 그리고 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 개념이 바탕이 됩니다.
'Swift' 카테고리의 다른 글
| Swift - Class와 Struct, 성능 관점에서 다시 보기 (1) | 2026.04.24 |
|---|---|
| Swift - @frozen과 @unknown default (0) | 2026.04.22 |
| Swift - Value Semantic (0) | 2026.01.19 |
| 앨범 만들기 6편 - AVFoundation이란? (0) | 2025.11.28 |
| 앨범 만들기 5편 - Transitioning Delegate 를 통한 화면전환 애니메이션 구현편 (0) | 2025.11.12 |
- Total
- Today
- Yesterday
- 역전파
- opaquetype
- BoxedType
- CacheLocality
- Concurrency
- webrtc
- 동시성
- SwiftUI
- iphone
- tuist v4
- 인공신경망
- GradientDescent
- coredata
- opaque
- 스위프트
- Algorithm
- kakaomapssdk
- 경사하강법
- AI
- ios
- 알고리즘
- nonisolated
- Actor
- Xcode
- SocialLogin
- Tuist
- swift
- CD/CI
- 코어데이터
- ViewBuilder
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
