SwiftUI 성능 프로파일링 with Instruments (실용 가이드)
Source: Dev.to
프로파일링을 하지 않으면, 추측하게 됩니다.
SwiftUI에서 추측은 무작위 .id() 해킹, 불필요한 EquatableView, 깨진 애니메이션, 알 수 없는 끊김, 그리고 시기상조의 마이크로 최적화로 이어집니다.
이 실용적이고 불필요한 얘기가 없는 가이드는 Instruments를 사용해 SwiftUI 앱을 프로파일링하는 방법을 보여줍니다:
- 어디를 살펴봐야 하는지
- 무엇이 중요한지
- 무시해야 할 것
- 결과를 해석하는 방법
- 실제 문제를 해결하는 방법
이 가이드는 “느려 보인다” 에서 “왜 그런지 알겠다” 로 이끌어 줍니다.
🧠 핵심 원칙
먼저 측정하고, 그 다음 최적화한다.
전형적인 SwiftUI 성능 문제:
- 레이아웃 스러싱
- 과도한 body 재계산
- 메인 스레드 차단
- 메모리 churn
- 뷰 아이덴티티 오용
Instruments가 이 모든 문제를 밝혀줍니다.
🧰 실제로 필요한 4가지 Instruments
나머지는 무시하세요. 다음부터 시작합니다:
- Time Profiler
- SwiftUI
- Allocations
- Leaks
이 네 가지가 SwiftUI 문제의 약 95 %를 커버합니다.
⏱️ 1. Time Profiler — 실제 병목 찾기
열기 방법: Xcode → Product → Profile → Time Profiler
확인할 항목
- 메인 스레드에서 오래 걸리는 블록
- 동일한 함수에 대한 반복 호출
- 무거운 JSON 또는 이미지 디코딩
- 레이아웃 루프
주의할 점
body가 과도하게 호출되는 경우ViewGraph업데이트- 비용이 많이 드는 모디파이어
.task내 동기 작업
❌ 흔히 보이는 Time Profiler 패턴
body 안에서 무거운 작업
var body: some View {
let data = heavyComputation()
…
}
수정 방법
@State private var data: Data
.task {
data = await heavyComputation()
}
동기 디코딩
let image = UIImage(data: data)
수정 – 메인 스레드가 아닌 곳에서 디코딩 (예: 백그라운드 큐 또는 async API 사용).
🧬 2. SwiftUI Instrument — 뷰 업데이트 추적
활성화: Instruments → SwiftUI
다음과 같은 정보를 확인할 수 있습니다:
- 어떤 뷰가 다시 렌더링되는지
- 얼마나 자주 렌더링되는지
- 왜 업데이트되는지
레드 플래그
- 아무 변화도 없는데 뷰가 업데이트되는 경우
- 큰 서브트리가 무효화되는 경우
- 아이덴티티가 리셋되는 경우
- 예상치 못한 애니메이션
🧠 SwiftUI Instrument에서의 레드 플래그
- 부모 뷰 업데이트가 전체 화면 재그리기를 일으킴
- 작은 상태 변화가 전체 리스트를 무효화함
- 레이아웃 패스가 반복됨
- 뷰가 업데이트되지 않고 재생성됨
이는 보통 아이덴티티 문제, 잘못된 상태 위치 지정, 혹은 환경 사용 오류를 나타냅니다.
🧪 3. Allocations — Memory Churn
Open: Instruments → Allocations
Look for
- 빠른 객체 생성 급증 (예: 스크롤 시)
- 반복적인
ViewModel생성 - 이미지 churn, 작업 churn
이러한 패턴은 메모리 누수, 과다 할당, 그리고 성능 저하 요인을 드러냅니다.
❌ Classic Allocation Problems
Creating objects in body
var body: some View {
let formatter = DateFormatter()
…
}
Fix
private let formatter = DateFormatter()
or inject the formatter.
Recreating ViewModels
SomeView(viewModel: ViewModel())
Fix
@StateObject private var vm = ViewModel()
🧯 4. Leaks — 메모리 해제 확인
Open: Instruments → Leaks
앱을 탐색하면서(뷰를 푸시/팝하고, 기능을 열고/닫고, 탭을 전환하는 등) 절대 해제되지 않는 객체를 주시하세요—보통 ViewModels, 서비스, 혹은 컨테이너가 해당됩니다. 만약 deinit되지 않으면 메모리 누수가 발생한 것입니다.
🧭 5. 화면 프로파일링 방법 (단계별)
- Instruments를 열고 Time Profiler + SwiftUI를 선택합니다.
- 실제 기기에서 문제 화면으로 이동합니다 (시뮬레이터에서는 절대 하지 마세요).
- 상호작용: 스크롤, 탭, 애니메이션, 데이터 로드.
- 기록을 중지합니다.
- 검사: 메인 스레드 활동, SwiftUI 업데이트, 할당.
🧠 6. SwiftUI 재렌더링 해석
스스로에게 물어보세요:
- 어떤 상태가 변경되었나요?
- 왜 이 뷰가 무효화되었나요?
- 정체성이 변경되었나요?
- 환경이 원인인가요?
- 부모가 자식을 무효화하고 있나요?
답을 찾지 못한다면, 아키텍처를 리팩터링해야 할 가능성이 높습니다.
🧬 7. 레이아웃 스래싱 감지
증상
- 스크롤 시 높은 CPU 사용
- 반복되는 레이아웃 패스
- 끊기는 애니메이션
Instruments는 레이아웃 함수(예: LayoutComputer)에 대한 반복 호출을 표시합니다.
해결 방법
- 중첩된 스택 감소
GeometryReader남용 피하기- 프리퍼런스 루프 제거
- 뷰 계층 구조 평탄화
🧪 8. 애니메이션 프로파일링
Use Core Animation and Time Profiler to look for:
- 프레임 손실
- 애니메이션 중 레이아웃 작업
- 전환 블록 내 무거운 수정자
Common fixes
- 작업을 애니메이션 블록 밖으로 이동
- 레이아웃을 변경하는 애니메이션을 피함
- 값을 미리 계산
🧠 9. 실제 환경 프로파일링 체크리스트
다음 시나리오를 프로파일링하세요:
- 콜드 런치
- 긴 리스트 스크롤링
- 네비게이션 푸시/팝
- 탭 전환
- 딥링크 진입
- 백그라운드 → 포그라운드 전환
- 화면 회전
이러한 상황이 대부분의 성능 문제를 드러냅니다.
❌ 10. 일반적인 안티‑패턴
- 디버그 빌드에서만 프로파일링하기
- Instruments 경고 무시하기
- 근거 없이 최적화하기
- SwiftUI를 무조건 비난하기
- 무작위 수식어를 사용해 “jank”를 고치려 하기
- 프로파일링 없이 배포하기
Performance is not optional. (성능은 선택 사항이 아닙니다.)
🧠 멘탈 모델
User Action
→ State Change
→ View Invalidation
→ Layout
→ Render
→ GPU
Instruments는 어느 단계가 깨졌는지 보여줍니다.
🚀 최종 생각
Instruments는 “느리게 느껴진다” 를 “이 함수가 메인 스레드를 32 ms 동안 차단합니다.” 로 바꿔줍니다.
정기적으로 사용하면 다음과 같은 효과가 있습니다:
- 더 나은 아키텍처
- 높은 코드 품질
- 버그 감소
- SwiftUI 앱에 대한 더 큰 신뢰