티스토리 뷰
하루에도 수십 번씩 우리는 스마트폰을 사용한다.
화면을 터치하고, 스크롤하고, 버튼을 누르면 앱이 반응한다.
그런데 가만 생각해 보면... 앱은 어떻게 계속해서 유저의 입력을 기다리고, 이벤트를 놓치지 않고 처리할까?
설마 단순히 while true {} 같은 무한 루프를 돌리는 걸까..?
그렇다면 CPU를 계속 쓰면서 배터리를 엄청 잡아먹을 텐데...
정답은 바로 Run Loop에 있다.
Run Loop는 이벤트가 없을 때는 대기 상태로 유지하고,
이벤트가 들어오면 즉시 반응할 수 있도록 관리하는 핵심 메커니즘이다.
오늘은 Run Loop가 iOS에서 어떤 역할을 하는지,
그리고 언제 우리가 직접 Run Loop를 컨트롤해야 하는지 알아보자!
Run Loop란? – 이벤트를 계속 처리하는 메커니즘
앱이 실행되면 단순히 한 번 코드가 실행되고 끝나는 게 아니라,
계속해서 유저 입력(터치, 제스처, 키보드 입력 등)을 기다리고, 이벤트를 처리하는 루프가 돌아간다.
Run Loop는 기본적으로 "이벤트 대기 & 처리"를 반복하는 루프다.
- 입력(Event)이 들어오면 처리한다.
- 처리할 이벤트가 없으면 대기 상태로 들어간다.
- 새로운 이벤트가 들어오면 다시 처리한다.
Run Loop가 없으면?
- 앱이 이벤트를 한 번만 처리하고 종료될 것
- iOS 앱에서 터치, 키보드 입력, 타이머, 네트워크 응답 같은 이벤트를 받을 수 없음
- UI가 업데이트되지 않거나, 앱이 멈춘 것처럼 보일 수 있음
즉, Run Loop가 없으면 앱이 제대로 동작할 수 없다.
그러면, 이게 iOS에서 실제로 어떻게 동작할까?
Run Loop의 동작 원리
Run Loop는 기본적으로 한 개의 스레드마다 하나씩 존재한다.
즉, 메인 스레드(Main Thread)에도 Run Loop가 있고,
백그라운드 스레드(Background Thread)에도 Run Loop를 만들 수 있다.
Run Loop의 기본 동작 흐름
- 실행 가능한 이벤트(터치, 타이머, 네트워크 요청 등)가 있는지 확인
- 이벤트가 있으면 실행
- 이벤트가 없으면 대기 상태(Idle)
- 새로운 이벤트가 들어오면 다시 실행
- 종료 신호가 오기 전까지 계속 반복
이걸 코드로 표현하면 이런 느낌이다.
while appIsRunning {
let event = waitForEvent()
process(event)
}
iOS에서 Run Loop는 CFRunLoop라는 Core Foundation 레벨에서 관리되며,
UIKit에서 이를 감싸서 RunLoop API로 제공한다.
Run Loop의 주요 구성 요소
Run Loop는 단순한 while 문처럼 보일 수 있지만, 내부적으로는 다양한 요소가 있다.
주요한 요소들을 알아보자.
1) Input Sources (입력 소스)
- 이벤트를 받아서 실행하는 역할
- 유저 이벤트(터치, 키보드 입력)나 시스템 이벤트(네트워크, 파일 처리 등)를 포함
- 대표적인 Input Source 예시:
- Port-Based Sources (CFMessagePort, Mach Port 등)
- Custom Sources (커스텀 이벤트 처리)
2) Timer Sources (타이머 소스)
- 정해진 시간이 지나면 실행되는 타이머 이벤트
- Timer 또는 DispatchSourceTimer로 생성 가능
- 일정한 간격으로 실행해야 하는 작업에 사용됨 (예: setInterval 같은 역할)
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in
print("2초마다 실행되는 코드")
}
3) Observer (관찰자)
- Run Loop의 특정 상태(시작, 종료, 대기 등)를 감지하고 특정 코드를 실행
- CFRunLoopObserver를 활용하여 감지 가능
let observer = CFRunLoopObserverCreateWithHandler(nil, CFRunLoopActivity.allActivities.rawValue, true, 0) { _, activity in
print("Run Loop 상태 변경: \(activity)")
}
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.defaultMode)
4) Run Loop Modes (모드)
Run Loop는 실행되는 모드(Mode)에 따라 동작이 달라진다.
모드를 다르게 설정하면 특정 이벤트만 처리하도록 할 수 있다.
| 모드 | 설명 |
| default | 기본 모드 (일반적인 이벤트 처리) |
| common | 여러 모드를 동시에 포함하는 모드 |
| tracking | UI 이벤트(스크롤, 드래그)만 처리 |
| UITrackingRunLoopMode | 터치 & 스크롤 중에만 실행됨 |
언제 Run Loop를 직접 컨트롤해야 할까?
대부분의 경우, Run Loop를 직접 컨트롤할 필요는 없다.
하지만 특정한 상황에서는 Run Loop를 다뤄야 한다.
1) 백그라운드 스레드에서 Run Loop 실행하기
메인 스레드에는 Run Loop가 자동으로 실행되지만,
백그라운드 스레드에서는 Run Loop가 기본적으로 활성화되지 않는다.
그래서 백그라운드에서 타이머나 네트워크 요청을 계속 실행하려면 Run Loop를 수동으로 돌려야 한다.
DispatchQueue.global().async {
let runLoop = RunLoop.current
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
print("백그라운드에서 실행 중...")
}
runLoop.add(timer, forMode: .default)
runLoop.run() // Run Loop를 실행 상태로 유지
}
run()을 호출하지 않으면 백그라운드 스레드가 바로 종료되어 타이머가 실행되지 않는다.
2) 네트워크 작업을 수행하는 스레드 유지하기
예를 들어, 특정 네트워크 작업이 완료될 때까지 스레드를 유지하려면
Run Loop를 활용해서 대기 상태를 만들 수 있다.
let runLoop = RunLoop.current
let stopDate = Date(timeIntervalSinceNow: 5.0) // 5초 동안 실행
while runLoop.run(mode: .default, before: stopDate) {
print("Run Loop 실행 중...")
}
3) 터치 이벤트나 애니메이션 중 특정 작업 실행
Run Loop의 모드를 변경하면 특정 이벤트 중에만 실행되도록 설정할 수도 있다.
예를 들어, UI 스크롤 중에는 특정 타이머가 멈추도록 만들 수 있다.
timer.add(to: .current, forMode: .common) // 일반적인 Run Loop 모드에서 실행
timer.add(to: .current, forMode: .tracking) // 스크롤 중에도 실행
정리 – Run Loop를 이해하면 앱이 보인다
Run Loop 핵심 요약
✅ Run Loop는 이벤트(터치, 네트워크, 타이머 등)를 처리하는 루프다.
✅ 메인 스레드에는 기본적으로 실행되지만, 백그라운드 스레드에서는 수동으로 돌려야 한다.
✅ Run Loop에는 Input Sources(이벤트), Timer Sources(타이머), Observer(상태 감지)가 포함된다.
✅ Run Loop Modes를 활용하면 특정 이벤트에서만 실행되도록 조절할 수 있다.
✅ 백그라운드 작업을 유지할 때, 특정한 작업이 끝날 때까지 기다릴 때 Run Loop를 직접 컨트롤할 수 있다.
결국, Run Loop를 이해하면?
- 앱이 어떻게 이벤트를 처리하는지 더 명확하게 이해할 수 있다!
- 백그라운드에서 타이머나 네트워크 요청이 종료되지 않는 이유를 알 수 있다!
- 메모리 & 성능 최적화를 위해 Run Loop를 효과적으로 활용할 수 있다!
Run Loop는 직접 다룰 일이 많지는 않지만,
iOS 앱이 내부적으로 어떻게 동작하는지를 이해하면 여러모로 좋을 것 같다!
'iOS' 카테고리의 다른 글
| OperationQueue - iOS에서 비동기 작업을 효율적으로 관리하는 방법! (0) | 2025.02.27 |
|---|---|
| Async/Await 완벽 정리 – 왜 써야 할까? 어떻게 써야 할까? (0) | 2025.02.26 |
| iOS에서 비동기 작업을 처리하려면? GCD(DispatchQueue)를 이해하자! (0) | 2025.02.25 |
| 캡처 리스트(Capture List) – 클로저가 변수를 기억하는 방법 (0) | 2025.02.21 |
| RxSwift - Concat 과 Merge (0) | 2024.02.26 |