https://www.youtube.com/watch?v=MTGh9U2yHNM
SwiftUI Run Loop란 무엇인가?
SwiftUI에서 UI 업데이트가 어떻게 이루어지는지 궁금해하신 적 있나요? 🤔
SwiftUI는 내부적으로 Run Loop라는 시스템을 통해 UI 업데이트를 관리합니다. 이 Run Loop는 Swift 5.5부터 Main Actor 위에서 동작하며, 다음과 같은 역할을 수행합니다:
- 사용자 이벤트 수신 (터치, 스와이프 등)
- 모델 업데이트 허용
- SwiftUI 뷰를 화면에 렌더링
이 과정을 "Run Loop의 틱(tick)"이라고 부르는데, 왜 이런 용어를 쓰는 걸까요? 마치 시계가 째깍째깍 소리를 내듯이, SwiftUI도 주기적으로 상태를 확인하고 UI를 업데이트하기 때문입니다!
@Published 프로퍼티의 동작 과정
@Published 프로퍼티가 변경될 때 실제로 무슨 일이 일어나는지 단계별로 살펴보겠습니다.
1단계: 데이터 할당
@Published var items: [Photo] = []
// 새로운 데이터를 가져와서 할당
self.items = fetchedPhotos
2단계: objectWillChange 이벤트 발생
@Published는 Combine 프레임워크의 Publisher 프로토콜을 따르는 프로퍼티 래퍼입니다. 값이 변경되면 willSet시점에 objectWillChange 이벤트를 방출합니다.
저는 처음에 이게 값이 변경된 후에 발생하는 줄 알았는데, 실제로는 변경되기 전에 발생하더라고요! 이게 중요한 이유는 뒤에서 설명드릴게요 👇
3단계: 스냅샷 생성
SwiftUI는 objectWillChange 이벤트가 발생하면 즉시 현재 상태의 스냅샷을 생성합니다. 이는 변경 이전의 상태를 저장하는 과정입니다.
4단계: 실제 값 할당
이제야 실제로 fetchedPhotos가 items에 할당됩니다.
5단계: 다음 Run Loop Tick에서 비교
SwiftUI는 다음 Run Loop 틱에서 현재 값과 이전에 저장한 스냅샷을 비교합니다. 값이 다르면 SwiftUI는 뷰를 업데이트해야 한다는 것을 알게 됩니다.
이런 방식으로 SwiftUI는 불필요한 렌더링을 방지하고 성능을 최적화할 수 있습니다!
중요한 점: 이 모든 과정은 메인 액터에서 일어나며, 순서가 보장됩니다. objectWillChange → 상태 변화 → Run Loop tick
기존 방식의 문제점: 메인 액터 블로킹
func fetchPhotos() {
// 🚨 이 함수가 네트워크 다운로드가 완료될 때까지 블로킹됨
let photos = downloadPhotosFromServer() // 동기적 실행
self.items = photos
}
네트워크 속도가 느린 경우 어떤 일이 일어날까요?
- 메인 액터가 블로킹됨 !!- fetchPhotos가 완료될 때까지 다른 작업을 할 수 없음
- objectWillChange 시점 문제 - 스냅샷을 찍었지만 다음 Run Loop tick과 비교했을 때 아직 차이가 없음
- UI 히치(Hitch) 발생 - 사용자에게는 UI가 끊기거나 버벅거리는 것으로 보임
저도 처음에는 "그냥 메인 큐에서 실행하면 되는 거 아닌가?"라고 생각했었는데, 실제로는 메인 액터가 블로킹되면서 문제가 발생하더라고요 😅
과거의 해결 방법
Swift 5.5 이전에는 이런 식으로 해결했습니다:
DispatchQueue.global().async {
let newPhotos = fetchPhotos()
DispatchQueue.main.async {
self.items = newPhotos // UI 업데이트
}
}
하지만 이 방식은 개발자가 직접 main.async 호출을 관리해야 하는 불편함과 실수 가능성이 있었습니다.
Swift 5.5 async/await의 해결책
Swift 5.5에서 도입된 async/await는 이 문제를 우아하게 해결했습니다!
@MainActor
func updateItems() async {
let newPhotos = await fetchPhotos() // ✅ 메인 액터를 일시적으로 해제 (yield)
self.items = newPhotos // ✅ fetchPhotos() 완료 후 다시 메인 액터로 복귀
}
await 키워드의 핵심은 스레드를 블로킹하지 않는다는 것입니다!
- 기존 방식: 메인 스레드가 fetchPhotos가 끝날 때까지 완전히 멈춤
- await 방식: 메인 액터를 일시적으로 해제하여 SwiftUI의 Run Loop가 계속 실행됨
이를 "메인 액터를 양보(yield)한다"고 표현합니다. 마치 양보운전을 하듯이, 메인 액터가 잠시 다른 작업에게 길을 내어주는 거죠!
실행 순서 보장
네트워크 요청이 완료되어 fetchPhotos가 값을 반환하면:
- Swift가 다시 메인 액터로 복귀
- 프로퍼티 값을 변경 (self.items = newPhotos)
- objectWillChange 이벤트 발생
- UI 상태 업데이트
이제 개발자가 직접 스레드 관리를 할 필요 없이, Swift가 자동으로 스레드 전환을 관리해줍니다! 🎉
'면접준비' 카테고리의 다른 글
NSObjcet 음.. SwiftUI에선? (0) | 2025.02.16 |
---|---|
명령형과 선언형, 그리고 FP까지 (0) | 2025.02.16 |
메모리관리(weak self와 guard의 만남) (0) | 2025.01.12 |
Hash-Hashable을 곁들인 (1) | 2025.01.05 |
SilentPush&RichPush (2) | 2024.11.08 |