본문으로 건너뛰기

Xcode Instruments에서 SwiftUI 뷰 다시 그리기와 원인 추적하기

SwiftUI Performance: Find Redraws in Xcode Instruments

www.avanderlee.com

핵심 요약

  1. SwiftUI 템플릿으로 짧고 집중된 상호작용을 기록한 뒤 View Body Updates 계층과 표에서 뷰별 업데이트 횟수와 실행 시간을 함께 확인한다.
  2. Root Causes와 cause-and-effect 그래프를 따라가면 업데이트를 유발한 프로퍼티를 찾을 수 있으며, 잦은 업데이트와 느린 body 실행을 구분해 대응할 수 있다.
  3. 단일 body 업데이트가 짧아도 반복 횟수가 많으면 비용이 쌓이므로, 코드를 바꾼 뒤 동일한 상호작용을 다시 기록해 측정값을 비교해야 한다.

도입

SwiftUI 성능 문제는 뷰가 너무 자주 업데이트되거나 body 실행에 시간이 오래 걸리는 경우로 나타난다. Xcode Instruments의 SwiftUI 템플릿은 업데이트 시점과 비용, 원인을 측정한다. Release 구성으로 앱을 프로파일링하고 증상을 재현하는 짧은 상호작용을 기록하면, SwiftUI 작업이 hang이나 hitch와 맞물리는지 확인할 수 있다.

상세 요약

기록 준비

Xcode에서 Product → Profile(⌘I)을 선택하고 SwiftUI 템플릿을 고른다. 기본 Release 구성으로 프로파일링하며, Debug 빌드는 실제 사용자 코드와 다르게 동작해 측정값을 왜곡할 수 있다. 템플릿에는 SwiftUI instrument 외에 Time Profiler, Hangs, Hitches가 포함된다. 화면 열기나 목록 스크롤처럼 증상을 재현하는 상호작용 하나를 짧게 기록한다. 긴 기록은 데이터가 많아 처리 시간이 늘고 읽기도 어렵다.

SwiftUI 트랙 읽기

기록을 멈추면 타임라인 위쪽에 SwiftUI 트랙이 나타난다. Update Groups 레인은 SwiftUI가 작업한 시점을 보여준다. CPU 사용량이 치솟는데 이 레인이 비어 있다면 문제는 SwiftUI 바깥에 있을 가능성이 있다. 아래에는 Long View Body Updates, Long Representable Updates, Other Long Updates가 표시된다. 주황색과 빨간색은 hitch나 hang에 기여할 가능성이 있는 업데이트를 나타낸다.

하단 요약은 Instruments가 긴 업데이트로 분류한 항목만 집계한다. 예시로 든 27초 기록에는 긴 업데이트 228건, 총 1.10초가 표시됐다. body 실행이 짧은 뷰가 수백 번 업데이트되는 경우는 이 요약에서 두드러지지 않을 수 있다.

뷰별 비용과 빈도 확인

View Body Updates 트랙에서 확인할 구간을 선택하면 상세 보기에서 해당 범위의 업데이트를 볼 수 있다. ⌘3은 View Body Updates View Hierarchy, ⌘4는 표로 전환한다. 계층은 선택 구간에서 실행된 뷰와 누적 시간을 보여준다. 예시에서는 전체 body 업데이트가 24.53ms였고 WorkloadCard는 8.35ms, InvalidationWorkloadCard는 6.71ms를 차지했다.

Call Tree 필터에서는 스레드별 분리, 시스템 라이브러리 숨기기, 수정자·기본 요소 숨기기 등을 설정할 수 있다. 시스템 라이브러리를 숨기면 앱의 뷰를 중심으로 확인할 수 있다. 표는 개별 업데이트마다 시작 시각, 지속 시간, 모듈, 뷰 이름, 설명, Root Causes 등을 보여준다. 예시에서 두 뷰의 body는 몇 밀리초 간격으로 반복 실행됐지만, 각 업데이트는 약 40~50µs에 불과했다. body는 메인 스레드에서 실행되므로 업데이트 하나가 짧더라도 반복되면 비용이 누적된다.

Root Causes 열은 업데이트를 일으킨 원인을 표시한다. 예시에서는 @Observable WorkloadController.(Int)가 나타났다. 관찰 대상의 Int 프로퍼티가 바뀌면서 해당 값을 읽는 뷰의 body가 다시 실행된 것이다. 행을 선택하면 검사기에서 시작 시각, 지속 시간, 뷰 계층을 확인할 수 있다.

원인에 따라 수정하고 다시 측정

더 많은 맥락이 필요하면 Update Group의 검사 범위를 설정하고, 업데이트 위에 포인터를 둔 뒤 화살표에서 Show Causes를 선택한다. 그래프의 파란 노드는 앱 코드 객체, 회색 노드는 시스템이 정의한 객체다. 그래프가 보여주는 의존성과 UI에서 예상한 동작을 비교한다.

원문은 과도한 업데이트의 사례로 다른 프로퍼티까지 관찰하는 객체 때문에 뷰가 갱신되는 경우와, 뷰 업데이트가 의미 있는 UI 변화 없이 하위 뷰까지 갱신하는 경우를 든다. 비싼 업데이트라면 무거운 작업을 body 밖으로 옮겨 데이터가 바뀔 때 수행하고, 뷰는 결과를 렌더링하도록 한다. 변경 후에는 같은 상호작용을 다시 기록해 업데이트 수나 시간이 실제로 줄었는지 확인한다.

RocketTrace는 Instruments 기록을 분석하는 Mac 앱이다. 앱을 직접 기록하거나 기존 trace를 가져올 수 있고 분석은 Mac에서 로컬로 실행된다. broad invalidation fan-out, 잦은 상태 변경, 긴 body 업데이트, 값 변경이 기록되지 않은 redraw, 일정한 주기의 업데이트, 가장 비용이 큰 뷰, 반복되는 런타임 경고 등 일곱 패턴을 확인한다. 각 결과에는 trace에서 측정한 근거가 포함되지만, 해당 패턴이 문제인지 UI에서 예상된 동작인지는 사용자가 판단한다.

결론

Instruments에서는 SwiftUI 트랙의 긴 업데이트만 보는 데 그치지 말고, View Body Updates 계층과 표에서 뷰별 시간과 반복 빈도를 함께 살펴야 한다. Root Causes와 원인 그래프로 트리거를 찾은 뒤, 잦은 업데이트인지 느린 body인지에 맞춰 코드를 수정한다. 같은 상호작용을 재기록해 업데이트 수나 실행 시간이 줄었는지 검증한다.