제목은 연막이고 사실은 제가 Instruments에게 매섭게 혼났습니다...
안녕하세요! 라이빗의 iOS 개발자입니다. 오랜만에 글을 쓰네요!
라이빗은 요즘 5차 스프린트를 바쁘게 소화하고 이제는 릴리즈를 준비하고 있습니다.
iOS의 경우 4차 스프린트 이후 기획되어 5차 스프린트까지 한번에 구현하는 대장정을 거쳤습니다. (1.0.0 출시 많관부)
이렇게 열심히 기능 구현만 하다보니 음... 너무 기능 구현만을 위한 코드를 작성하고 있지 않나? 싶은 마음이 들더라구요.
모름지기 iOS 개발자라면 유저의 입장에서 생각해보며 사용성을 높이기 위해 노력해야 하는데, 과연 iOS 개발자로서 코드 레벨에서 유저의 사용성을 높여줄 수 있는 방안이 뭐가 있을지 고민해보게 되었습니다.
그렇게 열심히 고민하다 Instruments라는 성능 분석 도구를 발견하게 되었어요!
Instruments를 통해서는 메모리 성능, UI Hang 등등 여러 방면의 성능을 지표로 파악할 수 있는데요.
이번에는 아주 간단하게 UI Hang을 분석해보고, 어떻게 개선해야 하는지 정도만 공부해보았습니다.
Instruments는 무엇인지, (제 입장에서) 왜 성능 개선을 해야하는지, 그럼 왜 Instruments를 써야 하는지, Hang은 뭐고 어떻게 분석하는지(feat. WWDC)에 대해 말씀드리도록 하겠습니다. 그럼 시작해볼까요!
제 친구 클로드에게 물어봤더니 Apple이 제공하는 성능 분석 도구로, 앱의 런타임 동작을 측정하고 문제를 진단할 때 사용한다고 합니다.
Instruments를 사용하면 여러 가지 문제를 진단하고 개선하는 데 활용할 수 있는데요, 대표적인 예시는 다음과 같습니다.
- 앱이 버벅이거나 프레임 드랍이 발생할 때 Time Profiler로 CPU 병목 찾기
- 메모리 사용량이 계속 증가하거나 앱이 크래시 날 때 Allocations와 Leaks로 메모리 누수 탐지하기
- 배터리 소모가 심하다는 피드백을 받았을 때 Energy Log로 원인 분석하기
- 네트워크 요청이 느리거나 타이밍 이슈가 있을 때 Network 템플릿으로 확인하기
제 경우에는 런타임에서 UI에 일어나는 Hang을 찾는 게 목적이라서, SwiftUI와 Hangs, Hitches 항목을 이용했습니다.
(이게 뭐고 어떻게 이용하는지는 좀 이따가...)
그렇다면 이 Instruments, 뭔지는 알겠는데 이걸 왜 써야 할까... 에 대한 의문이 좀 있었습니다.
정확히는 "이런 것까지 써가면서 성능 개선을 해야 할까?" 라는 의문이었어요.
대부분의 사용자가 잘 사용하고 있는데, 이렇게 분석 도구까지 써서 발견하는 사소한 수치들을 개선해야 하는 이유에 대해서는 리소스 낭비라는 생각도 들었구요.
그래서 조금 더 프로덕트 자체의 관점에서 바라보며 이 의문을 해소하려고 했습니다.
결론적으로는 다음과 같은 생각을 했어요.
조금 다른 이야기일 수도 있지만 제가 생각하는 좋은 iOS 개발자의 자세란 유저를 생각하는 개발자였거든요.
유저를 생각하는 자세에 대해 곰곰이 고민하다 보니 최대한 많은 유저가 최상의 경험을 할 수 있는 프로덕트를 제공하는 것! 이라는 생각을 하게 되었습니다.
최대한 많은 유저가 최상의 경험을 할 수 있는 프로덕트... 는 뭘까? 어떻게 만들까? 를 고민해보니
최대한 완성도 높은 환경을 모든 유저에게 동일하게 제공할 수 있는 프로덕트라는 결론에 이르렀습니다.
리소스를 핑계로 유저 경험을 무시한 것 같아 반성도 좀 했습니다 ㅎㅎ
AI를 잘 활용하는 것이 좋은 개발자의 기본이 된 상황에서, 그럼에도 개발자가 필요한 이유에 대해 요새 고민을 많이 하고 있는데요...
결론적으로는 AI로 해결할 수 없는 문제 정의를 위한 도구라는 생각을 했습니다.
AI는 사실 프로젝트의 모든 맥락을 이해할 수 없기에 이런 부분에서 개발자가 문제를 캐치하고, 어떻게 하는 게 가장 리소스를 줄이면서 문제를 빠르게, 잘 해결할 수 있는가? 에 대한 고민을 계속 해야 하는 것 같아요.
그런 의미에서 문제를 정의하는 것이 개발자의 가장 중요한 기본 소양이라고 생각했습니다.
결국 Instruments는 앱의 실제 동작 데이터를 보여주는 프로파일링 도구입니다.
시각적으로 문제를 보여주기 때문에 원인을 빠르게 캐치해서 병목을 해결할 수 있다는 점이 개발자의 문제 정의에 큰 도움이 될 거라고 생각했어요!
개인적으로는 최근에 지인 분께 받았던 당근의 아티클 신입 모바일 엔지니어로 ‘당연한’ 사용자 경험을 만들어간다는 건을 많이 참고했는데요.
읽으면서 그동안의 제 태도를 많이 반성했습니닷.
이후 작업할 때도 여러 가능성에 집중하면서 더 멋진 사용자 경험을 제공하며 유저의 이탈을 최소로! 줄이는 것을 목표로 해야겠다는 생각이 들었는데.. 한번 읽어보시길 추천드려요!
그럼 이제 진짜 오늘의 주인공인 Hang에 대해서 알아볼까요?
Instruments에서는 여러 도구를 제공하고 있지만 처음이기도 하고 해서 이번 작업에서는 가볍게 제가 가장 빠르게 판단할 수 있는 부분인! UI Hang 잡는 것을 시도해 보았습니닷.
WWDC23에서 공개된 Analyze hangs with Instruments 영상과 KWDC24에서 공개된 당신의 View가 버벅이는 이유(feat. Instruments) 영상을 많이 참고했는데요. 시간 되시면 한번 보시는 것을 추천드립니다.

만약 전구에 전원을 연결했을 때 불이 딸깍! 하고 바로 켜지는 게 아니라 어느정도의 지연을 유발한다면, 사람은 어느 정도의 지연을 "부자연스럽다" 라고 느낄까요?

일반적으로 사람은 100ms까지는 즉각적인 반응이라고 느낀다고 합니다.
100ms부터 250ms까지는 지연이 됨을 인지하지만 어느정도 상황에 따라 용인된다고 보구요.
250ms부터 500ms까지는 대부분의 도구가 hang으로 보고하기 시작하며, 이를 마이크로 행이라고 부른다고 합니다.
우리가 말하는 일반적인 행의 정의는 500ms부터 시작입니다.
사용자에게 최상의 경험을 제공하려면 100ms 이하의 지연을 목표로 해야겠죠.
단, request-response 구조의 상호작용에서는 500ms까지의 지연이 허용된다고 합니다.
이번에는 즉각적인 경험 제공을 위해 100ms 미만으로 행을 잡아보는 걸 목표로 했습니다.
이벤트 처리와 렌더링 루프에 대한 내용도 떱떱디시에는 나와있으니 시간이 되신다면 한번 보시는 것을 추천드려요!
바로 이걸 위해서 Instruments를 이용하는 겁니다!!!!!!!!!!!!!!!!!!!!!!!!!!!
그치만 아직 Instuments를 읽을 줄 모르는 저같은 초보를 위해 떱떱디시에서는 원인을 파악하는 방법도 설명해주고 있습니닷.
일단 Xcode에서 Instruments를 보기 위한 일련의 과정들을 빠르게 설명해드릴게요.
- 엑스코드에서 cmd + i를 눌러줍니다.
- 뭔가 뜨는 화면이 있을텐데, 커서를 쭉 내려서 SwiftUI를 선택한 후 확인을 클릭해줍니다.
- 그럼 또 뭔가 화면이 뜰텐데, 왼쪽 상단의 D 처럼 생긴 빨간 버튼을 눌러줍니다.
- 열심히 화면을 움직이면서 측정합니다.
이렇게 하면 결과 화면이 짜잔~ 하고 눈앞에 나타나는데요.
이제 캡처 화면과 함께 행을 분석하는 방법에 대해서 알아보겠습니다.

화면을 보시면 벌써부터 어지러운 그래프가 보입니다.
여기서 우리가 집중해야 할 건 당근 Hangs입니다. 제가 측정한 뷰에는 두개의 Hang이 찍혀 있네요!
하나를 확대해서 살펴보겠습니다.

확대해보니 Brief Unresponsiveness라고 적혀있네요.
이건 아까 말했던 100ms부터 250ms까지의 행? 까지는 아니지만 어느정도의 지연이 일어나는 상태를 뜻합니다.
실제로 123.85ms로 100ms에서 250ms 사이 값임을 보여주고 있습니닷.
그러면 왜 행이 발생했을까요?
떱떱디시에서는 이렇게 메인 스레드가 응답하지 않아 행이 발생하는 두가지 주요 원인을 아래와 함께 설명합니다.
1. 메인 스레드가 너무 바쁨 (busy)
이 경우에는 메인 스레드가 다른 작업을 하느라 바빠서 CPU 활동이 높게 나타나는 경우인데요. 사진의 Time Profiler에서 보이는 CPU Usage를 통해 이 경우인지 파악이 가능합니다.
2. 메인 스레드가 블락됨 (blocked)
이 경우에는 메인 스레드가 다른 곳의 작업이 완료되기를 기다리느라 막혀있는 경우인데요, 이 경우 CPU 활동이 매우 낮거나 없이 나타나 판별이 가능합니다.

저희의 경우에는 (사진 상에는 좀 작아보이지만...) 무려 105%에서 530%를 왔다갔다 하고 있어 1번의 경우임을 알 수 있습니다.
그렇다면 메인 스레드가 왜 바빠졌을까요? 이 부분도 떱떱디시에서 아주 친절하게 설명해주고 있습니다.

CPU 시간이 많이 소요되는 경우의 두 가지 가능성은 다음과 같아요.
장시간 실행(Long-running)
해당 메서드 자체가 오랜 시간 실행되는 경우 (예: 거북이 함수). 이 경우, 메서드의 구현과 하위 호출(callees)을 조사하여 작업을 줄여야 합니다.
과도한 호출(Called a lot of times)
해당 메서드가 반복적으로 많이 호출되는 경우 (예: 유니콘 함수). 이 경우, 호출하는 상위 코드(caller)를 조사하여 호출 횟수를 줄여야 합니다.
해당 가능성에 대해서도 측정 가능하면 좋겠지만 아쉽게도 Time Profiler가 그런 케이스까지 말해주지는 않는다고 합니다.
os_signposts를 활용하면 특정 합수의 실행 시간을 측정할 수 있는데, 저는 처음이라 거기까지는 해보지 않았어요.
대신 SwiftUI에서 제공하는 View Body를 이용해 보았습니다.

아까 행이 일어난 부분의 사진을 다시 볼까요? 일단 모종의 이유로 CPU Usage가 높아졌다는 것까지는 이해가 됩니다.
View Body를 보니 가장 상단에 Transaction for gesture라는 긴 바가 보이네요. 행의 가장 많은 부분을 차지하고 있습니다.
뭔지는 모르겠지만 아래에 불길한 빨간 바들도 몇개 보입니다.
의미를 해석해볼게요.
먼저 Transaction for Gesture라는 바는 사용자의 제스처(탭)로 인한 업데이트가 시작되었음을 뜻합니다.
터치했네? 정도로 이해해주시면 됩니다.
Long View Body Updates에서 빨간 바가 5~6개 연속으로 나타나는 건 여러 View Body가 각자에게 할당된 1프레임 내에 렌더링되지 못하고, 시간을 초과했음을 의미합니다.
해당 부분이 이해가 잘 가지 않아 제 친구 클로드에게 물어보았는데요.

요런 느낌이라고 하네요.
정리하면 1프레임 당 16.67ms 안에 계산 및 렌더링을 끝내야 하는데, 못 끝낼 경우 프레임이 드롭되어 저렇게 빨간 바처럼 보인다는 겁니다!
그렇다면 여러 상황을 종합해봤을 때 행이 발생한 주요 원인은 제스처로 인한 여러 View Body가 동시에 업데이트되면서, 각 뷰의 프레임 계산 시간이 초과된 게 누적된 것! 이라고 볼 수 있겠습니다.
그럼 대체 어느 뷰가 문제인가? 라고 생각하실 수 있는데, Instruments에 답이 나와있습니다.

이렇게 쨘~하고 어떤 뷰가 문제인지 친절하게 가르쳐주걸랑요. 문제는 콘서트 정보를 보여주는 세그먼트 뷰, ConcertInfoTabView였습니다.
문제를 한번 추정해보겠습니다. ConcertInfoTabView가 호출되는 시점은 콘서트 상세 페이지에 진입하는 시점인데요.
해당 부분 코드는 다음과 같습니다.
struct ConcertInfoTabView: View {
@Bindable var store: ConcertStore
var body: some View {
// 탭 전환 시 모든 탭 콘텐츠가 재렌더링
TabView(selection: $store.selectedTab) {
InfoTab() // body 실행
SetlistTab() // body 실행
CultureTab() // body 실행
}
}
}
메인 스레드는 계속 일하는 중이고, View Body 계산이 너무 어렵다면... 탭뷰에서 여러 뷰를 한번에 실행시키는 게 문제일 수 있겠습니다.
저는 그래서 LazyView를 사용해서 탭 컨텐츠를 지연 로딩시키는 방식으로 리팩토링을 진행해보기로 했어요!
이렇게 길고 길었던 Instruments로 UI Hang 혼내주기... 가 마무리되었습니다.
사실 아직 혼내주진 못했는데 이건 이슈 파서 진행해보고 직접 결과도 확인해보려고 합니다 ㅎㅎ
처음이다보니 분석도 어렵고 어떻게 읽어야 하는지도 잘 모르겠더라구요. 도움이 되셨으면 좋겠습니다.
제 경우에는 좀 더 원활한 분석을 위해 UI Hang 분석을 위한 스크립트도 제작해보았는데요.
관련한 PR 링크를 공유드리니 만약 도움이 필요하시다면 분석 파일을 해당 스크립트를 통해 실행하시면서 감을 잡아보시길 추천드립니다.
[Refactor] Instruments 분석 스크립트 구현 및 디자인 컴포넌트 업데이트
긴 글 읽어주셔서 감사합니다!!!!!!!!!!!!!!!!!!!!!