2025. 4. 7. 20:18ㆍSwift, iOS Foundation
Swift의 UI 작업은 Main Thread에서 동작해야한다!
아요 개발하다가 한 번쯤 아래와 같은 보라색 경고를 보신 적이 있을 겁니다.
해당 상황의 경우에는,
"오직 메인 스레드 (main thread only)에서만 사용할 수 있는 UIKit의 속성"을 "다른 스레드에서 접근"했기 때문에 발생했습니다.
꼭 아래의 UIViewController 속성이 아니더라도,
UIView, UINavigationController 등 UI, 즉 화면을 담당하는 객체 관련 코드가 메인 스레드가 아닌 다른 스레드 접근을 시도할 경우에
화면과 동일한 보라색 스레드 경고를 표출하게 될 겁니다.

UIKit 공식문서를 들어가봐도 Important 칸에 별도로 강조하면서 설명하고 있습니다.
"UIKit 클래스는 오직 메인 스레드 혹은 메인 디스패치 큐에서만 사용"해야 한다.
"화면을 그리는 요소는 메인 스레드에서 담당해야 한다"는 생각은 사실 그동안 너무도 당연하게 생각했던 것이에요.
하지만 여러분은 "왜 꼭 메인 스레드여야만 하지?" "다른 스레드에서 UI 작업을 돌릴 수는 없는걸까?"와 같은 의문을 가져본 적은 없으신지요.
사실 모든 것에 당연한 것은 없고, 그에 맞는 이유가 존재하는 것이니까요!
그래서 오늘 글에서는 "UI 작업이 반드시 메인 스레드에서 동작해야 하는 이유"에 대해 딥다이브를 해보는 시간을 가져보고자 합니다.

UIKit과 SwiftUI는 Thread-Safe 하지 않다.
💡 Thread-Safe 하다는 것은 여러 스레드에서 동시에 접근하더라도, 예상치 못한 동작이 발생하지 않는다는 것을 의미합니다.
iOS 개발의 화면을 담당하고 있는 대표 프레임워크 UIKit과 SwiftUI는 Thread-Safe하지 않습니다.
즉 이것을 풀어서 설명해 보면,
UIKit과 SwiftUI의 구성요소 (View, Frame, Constraints)를 동시에 여러 스레드에서 접근할 경우 예상치 못한 동작 (내부 데이터의 손상 또는 데이터의 충돌)을 일으킬 수 있다는 의미이죠.
예시를 들어보겠습니다.
1번 스레드에서는 View의 색을 빨간색으로 유지하도록 명령하고, 2번 스레드에서는 View의 파란색으로 변경하도록 명령한다고 해봅시다.
혹은 한 스레드에서 UIView의 frame을 변경하는동안, 다른 스레드에서 동시에 UIView의 tintColor를 수정하게 되는 경우도 생각해 봅시다.
이런 경우에, UIKit 혹은 SwiftUI의 View는 어떤 스레드의 명령을 들어야 할지 혼란에 빠지게 될 거예요.
아래 사진처럼 "얘두라 나 누구 말 들어"라고 질문을 던지겠죠?

이 상황에서 View가 완만하게는 두 스레드 중 하나의 명령만 어쩔 수 없이 수정사항을 반영하게 될 수도 있고요.
조금 과격하게는 두 지시사항을 동시에 이행하려다가 내부 메모리 손상까지 이어져 UI가 박살(?)날 수도 있을 겁니다.
이와 같이 여러 스레드가 동시에 동일한 메모리 위치를 읽거나, 쓰려고 할 때 발생할 수 있는 문제를 데이터 경합 (Data Race)이라고 부릅니다.
UIKit과 SwiftUI는 이 데이터 경합 (Data Race) 문제로부터 자유롭지 못하게, 다시 말해 안전하지 않게 설계되어 있습니다.
그렇다면 Apple은 왜 UIKit과 SwiftUI를 Thread-Safe하게 만들지 않은 걸까?
왜 Apple은 UIKit과 SwiftUI를 Thread-Safe 하게 만들지 않았을까요?
애초에 데이터 경합 (Data Race) 문제로부터 자유롭게 (= 안전하게) 만들었다면,
View를 만들 때 이런 예상치 못하게 발생할 수 있는 문제를 고민하지 않고 / 쉽게 만들 수 있을 것 같잖아요.
여기서는 Apple의 프레임워크 설계 철학이 나타나는 지점입니다.
Apple은 "UI는 단일 Serial Queue에서만 관리된다."라는 전제를 통해,
일관성 있고 효율적인 화면 렌더링과 애플리케이션 동작의 안정성을 보장하고 싶었을 것입니다.
만약 UIKit이나 SwiftUI와 같은 UI 프레임워크들이 Thread-Safe 하도록 설계되었다면,
내부적으로 동기화 메커니즘 (NSLock 등)을 사용해야하며, 이 메커니즘이 오히려 예상치 못한 성능 저하와 복잡성 증가, 디버깅 지옥을 만들었을지 모르거든요.
그래서 Apple은 개발자의 편의를 오히려 증가시키기 위해 UI 작업에 대해 Thread-Safe 하지 않도록 설계했지만,
작업할 수 있는 스레드를 메인 스레드로만 제한하여 발생할 수 있는 데이터 충돌 (Data Race) 문제를 해결하고자 한 것입니다.
Thread-Safe Class Design · objc.io
Concurrent Programming August 2013
www.objc.io
잠깐! 여러분은 메인 스레드 (Main Thread)에 대해 제대로 알고 계신가요?
메인 스레드 (Main Thread)는 iOS 앱이 실행될 때 기본으로 생성되는 스레드입니다.
UIKit의 AppDelegate.swift 파일이던, SwiftUI의 ~App.swift 파일이던 간에
앱의 실행 지점에 해당하는 파일에는 아래 보이는 것처럼 @main 어노테이션을 사용해 Swift 프로그램의 시작점 (entry point)을 지정하는 것 알고 계실 겁니다.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
...
@main
struct SwearApp: App {
var body: some Scene {
WindowGroup {
ListView()
}
}
}
사실, 이 시점부터 알게 모르게 우리 앱의 메인 스레드 (Main Thread)는 가동되기 시작합니다.
즉, 이때 실행된 메인 스레드는 앱 전체 생명 주기 내내 꺼지지 않고 살아있기 때문에 -> 앱에서의 모든 UI 작업과 이벤트 처리를 담당하는 것이죠.
조금 더 자세하게 들어가면,
iOS 앱은 실행 시 (@main) 기본적으로 하나의 메인 스레드를 생성하고, 이 메인 스레드가 앱의 주요 작업을 처리하는 Main Run Loop라는 것을 실행하게 되는데요.
내부적으로 Main Run Loop Cycle이 돌아가게 되면서, 지속적으로 메인 스레드가 이벤트를 기다리고 처리할 수 있는 것이고요. (터치 이벤트, 화면 업데이트 등의 UI 관련된 모든 작업은 이 Run Loop 내에서 순차적으로 처리되도록 설계되어 있습니다.)
앱이 멈추지 않고 동작하는 것처럼 보일 수 있는 것입니다.
참고로 Main Run Loop Mode에는 아래와 같은 모드들이 있습니다.
*Run Loop Mode란 특정 작업이나 이벤트 소스를 정의하기 위해 RunLoop가 동작하는 방식을 정의한 것. 한 사이클이 실행될 때 특정모드에서만 동작합니다.
- .default : 일반적인 터치 이벤트, 애니메이션 이벤트, UI 업데이트 등을 처리
- .tracking : 사용자가 인터페이스를 조작하거나 컨트롤을 추적하는 동안 (스크롤, 드래그와 같은 제스처 관련) 이벤트를 처리
- .common 모드도 있지만 복잡하니 그냥 넘어가도록 할게요.

뭐 이런저런 말이 길었지만 결국 하고 싶은 말은 아래와 같은 것들입니다.
- 메인 스레드 (Main Thread)는 Swift 프로젝트 (앱)가 처음 동작될 때 가동되기 시작해서 앱이 꺼질 때까지 멈추지 않고 동작한다.
- 앱의 핵심 UI 입력이나, 이벤트 처리는 "앱의 동작과 직접적으로 관련된 부분"이기 때문에 메인 스레드에서 처리하는 것이다.
- 만약, 메인 스레드가 멈춘다면 마치 앱의 동작이 멈춘 것처럼 보일 것이다.
정리!
✔️ UI와 관련된 Apple의 프레임워크는 Thread-Safe 하지 않게 설계되어 있습니다.
✔️ Apple의 UI 프레임워크는 Thread-Safe 하지 않게 설계되어 원래는 Race Condition으로부터 안전하지 못하지만,
이를 Main RunLoop를 통해 UI를 갱신하도록 설계함으로써 이 문제를 해결하고자 한 것이죠.
✔️ "UI는 메인 스레드에서만 동작해야 한다"는 대전제를 통해 Apple은 일관성 있고 효율적인 화면 렌더링과 애플리케이션 동작의 안정성을 보장한 것입니다.
Reference
Thread Safety Summary
Thread Safety Summary This appendix describes the high-level thread safety of some key frameworks in OS X and iOS. The information in this appendix is subject to change. CocoaGuidelines for using Cocoa from multiple threads include the following:Immutable
developer.apple.com
About Threaded Programming
About Threaded Programming For many years, maximum computer performance was limited largely by the speed of a single microprocessor at the heart of the computer. As the speed of individual processors started reaching their practical limits, however, chip m
developer.apple.com
UIKit | Apple Developer Documentation
Construct and manage a graphical, event-driven user interface for your iOS, iPadOS, or tvOS app.
developer.apple.com
Diagnosing memory, thread, and crash issues early | Apple Developer Documentation
Identify runtime crashes and undefined behaviors in your app during testing using Xcode’s sanitizer tools.
developer.apple.com
Data races | Apple Developer Documentation
Detects unsynchronized access to mutable state across multiple threads.
developer.apple.com
iOS: Why the UI need to be updated on Main Thread
Do you ever think about why UI really MUST to be updated on main thread? What will happened if we turn UIKit into thread-safe design?
medium.com