삐삐북 개발기 11편 — 앱이 켜지자마자 꺼진다: Crashlytics가 없었다면

"앱을 켜면 꺼져요"
1.2.0을 출시하고 얼마 되지 않아 리뷰가 하나 달렸습니다.
"앱을 켜면 바로 꺼져서 못 들어갑니다."
개발자가 가장 듣기 싫은 리뷰입니다. 앱이 실행조차 안 된다는 건 치명적입니다.
처음엔 특정 기기나 iOS 버전의 문제일 수도 있다고 생각했습니다. 하지만 같은 증상을 여러 명이 겪고 있을 수 있어서 빠르게 대응해야 했습니다.
Firebase Crashlytics
다행히 앱에 Firebase Crashlytics를 연동해두었습니다.
Crashlytics는 앱이 크래시(강제 종료)될 때 그 상황을 자동으로 기록하고 Firebase 콘솔로 전송합니다. 어떤 코드의 어느 라인에서 크래시가 났는지, 어떤 기기와 OS 버전에서 발생했는지를 볼 수 있습니다.
Crashlytics가 없었다면, 리뷰만 보고 막막하게 "뭐가 문제지?" 하며 찾아야 했을 겁니다.
두 가지 버그를 발견했다
Crashlytics 로그를 보니 크래시가 여러 건 기록되어 있었습니다. 크게 두 가지 원인이었습니다.
버그 1: 바코드 스캐너
업데이트 과정에서 카메라 관련 코드에 문제가 생겼습니다. 바코드 스캔 화면으로 이동할 때 크래시가 발생했습니다.
AVFoundation 세션 초기화 과정에서 충돌이 있었고, 이전 버전에서 잘 작동하던 코드가 업데이트 이후 문제가 됐습니다.
버그 2: 로컬 유저의 크래시
이게 더 심각했습니다.
로그인 없이 사용하는 로컬 유저가 "읽는 중" 상태로 책을 등록하면 앱이 크래시됐습니다.
원인을 찾아보니, Realm의 데이터 모델과 Firestore의 데이터 모델을 공통으로 처리하는 코드에서 문제가 있었습니다.
강제 언래핑(Force Unwrapping)이 문제였다
Swift에서 옵셔널(Optional) 타입은 값이 있을 수도, 없을 수도 있는 변수입니다.
! 연산자로 강제 언래핑을 하면 "무조건 값이 있다"고 가정하고 꺼냅니다. 그런데 값이 없으면(nil) 런타임 크래시가 발생합니다.
Firestore의 데이터 모델에는 있는 필드가 Realm 모델에는 없는 경우가 있었습니다. 로컬 유저의 데이터를 공통 로직으로 처리하면서 강제 언래핑을 했는데, Realm 모델에 없는 필드를 꺼내려다 nil이 나와서 크래시가 터졌습니다.
수정 과정
버그들을 파악하고 나서 수정 작업에 들어갔습니다.
바코드 스캐너: AVFoundation 세션 초기화 로직을 점검하고 재구성했습니다.
로컬 유저 크래시: 강제 언래핑을 옵셔널 바인딩(if let, guard let)으로 교체했습니다. 값이 없을 경우 크래시가 아니라 안전하게 처리되도록 했습니다.
그리고 Realm과 Firestore 모델 간의 차이를 더 꼼꼼하게 확인하고, 공통 로직에서 양쪽 모두 안전하게 동작하도록 수정했습니다.
2026년 6월 29일, 1.2.2 출시
1.2.0 이후 통계 화면 개편을 담은 1.2.1이 먼저 나왔고, 2026년 6월 29일 오전 1:59에 크래시 버그들을 전부 수정한 1.2.2가 출시됐습니다.
배운 것
세 가지를 다시 확인했습니다.
첫째, 강제 언래핑은 최소화해야 합니다. 특히 다른 구조의 모델을 함께 다루는 코드에서는 더욱 조심해야 합니다.
둘째, Crashlytics 같은 크래시 모니터링 도구는 필수입니다. 사용자 리뷰만으로는 원인을 찾기 어렵습니다.
셋째, 큰 업데이트 후에는 로컬 유저 시나리오를 반드시 테스트해야 합니다. 로그인 유저와 로컬 유저의 동작이 다르기 때문에, 두 경우를 모두 테스트하지 않으면 이런 버그가 생깁니다.