Debug(디버그 모드) — 컴파일러가 심볼 정보를 포함하고 코드 최적화를 비활성화하며 단계별 실행 분석을 위해 디버거를 연결하는 모바일 애플리케이션의 빌드 구성입니다. Android Developers에 따르면 Debug 빌드에는 디버깅 심볼이 포함되고 리소스를 압축하지 않으며 데이터베이스 및 네트워크 요청 인스펙터를 연결할 수 있습니다. Debug 모드는 Release 빌드와 대조됩니다: Debug에서 개발자는 코드 실행 투명성을 위해 성능을 희생합니다.
핵심 요점
Debug는 단순한 컴파일러 플래그가 아니라 애플리케이션을 개발자에게 투명하게 만드는 설정의 전체 집합입니다. Debug 모드에서 컴파일러는 실행 파일에 심볼 이름 테이블(DWARF)을 추가하여 기계 코드를 소스 라인에 연결합니다. 이 테이블이 없으면 디버거는 현재 어떤 코드 라인이 실행 중인지 표시할 수 없습니다.
디버거(debugger)는 제어된 환경에서 애플리케이션을 실행하는 프로그램입니다. 모든 라인에서 실행을 일시 중지하고(중단점), 현재 범위의 모든 변수 값을 보고, 즉석에서 변경하고, 실행을 계속할 수 있습니다. 모바일 플랫폼의 표준 디버거는 LLDB입니다—Xcode와 Android Studio 모두에서 사용되는 LLVM 구성 요소입니다.
Debug 모드에는 Release에서 비활성화된 추가 검사도 포함됩니다: 어설션, 배열 경계 검사, 메모리 누수 감지기, 확장 로깅. 이러한 검사는 애플리케이션을 느리게 하지만 개발 초기 단계에서 오류를 발견합니다—코드가 사용자에게 도달하기 전에.
Debug 빌드와 Release 빌드의 차이는 근본적입니다: 컴파일러 플래그, 서명 구성 및 패키징 설정의 두 가지 다른 세트입니다. 이러한 차이점을 이해하면 “시뮬레이터에서는 작동하지만 실제 기기에서는 작동하지 않는” 상황을 피하는 데 도움이 됩니다.
| 매개변수 | Debug | Release |
|---|---|---|
| 최적화 | 비활성화 (-O0) | 활성화 (-Os 또는 -O2) |
| 심볼 | 전체 DWARF 테이블 | 제거됨 |
| 서명 | 개발 인증서 | 배포 인증서 |
| 프로필 | Debug 프로비저닝 프로필 | App Store / Ad Hoc 프로필 |
| 로깅 | 전체 (모든 수준) | 비활성화 또는 최소 |
| 난독화 | 비활성화 | 활성화 (ProGuard/R8) |
| .apk/.ipa 크기 | 더 큼 (심볼 + 압축 없음) | 더 작음 (R8 + 리소스) |
Debug 빌드는 개발 및 로컬 기기 테스트의 모든 단계에서 사용됩니다. Release 빌드는 App Store Connect 또는 Google Play Console에 제출하기 전에 생성됩니다. Release 빌드에서 디버깅은 기술적으로 가능하지만 이름이 변경된 메서드(R8)와 크래시 로그의 symbolication 부족으로 인해 매우 불편합니다.
일반적인 문제는 Debug에서는 작동하지만 Release에서는 충돌하는 코드입니다. 원인은 컴파일러가 최적화 수준에 따라 다르게 처리하는 코드의 UB(정의되지 않은 동작)입니다. 일반적인 예: 초기화되지 않은 변수 읽기 또는 strict aliasing 위반. 이러한 오류를 감지하려면 각 Release 빌드 전에 정적 분석기(Clang Static Analyzer, ktlint)를 사용하십시오.
LLDB는 LLVM 기반의 고성능 디버거로 C, C++, Objective-C, Swift 및 Kotlin/Native를 지원합니다. LLDB는 REPL 인터페이스를 제공하여 중단된 애플리케이션 컨텍스트에서 임의의 표현식을 실행하고, 변수 값을 변경하고, 함수를 호출할 수 있습니다.
중단점은 디버거의 핵심 도구입니다. 코드 라인에 지점을 설정하면 실행이 해당 라인에 도달했을 때 애플리케이션이 일시 중지됩니다. LLDB는 여러 유형의 중단점을 지원합니다: 조건부(조건이 충족될 때만 트리거), 심볼릭(함수 호출 시), 일회성(한 번 트리거되고 자동으로 제거됨).
워치포인트는 변수 변경을 관찰하는 지점입니다. 메모리 주소를 지정하면 디버거가 해당 주소에 대한 쓰기 시 실행을 일시 중지합니다. 이 도구는 데이터 경합 및 공유 객체의 잘못된 변형을 찾는 데 필수적입니다. UIKit 계층을 보려면 Xcode에서 UIView Inspector를 사용하십시오.
// 조건부 중단점 설정
(lldb) breakpoint set --name "viewDidLoad" --condition "self.isViewLoaded == false"
// 속성에 워치포인트
(lldb) watchpoint set variable self->_loadingState
// 중단된 컨텍스트에서 코드 실행
(lldb) expr self.view.backgroundColor = UIColor.redColor
두 IDE 모두 LLDB 위에 그래픽 인스펙터를 제공합니다. Android Studio에는 Layout Inspector(View 계층), Network Inspector(HTTP 요청 추적) 및 Database Inspector(실시간 SQLite)가 포함됩니다. Xcode는 Debug Memory Graph(메모리 누수 분석) 및 View Debugger(UIKit 레이어의 3D 보기)를 제공합니다.
Android 11부터 Wi-Fi를 통한 디버깅은 USB 연결 없이 작동합니다: Android Studio에서 QR 코드를 스캔하면 됩니다. iOS는 Xcode 9+부터 Wi-Fi 디버깅을 지원합니다—기기가 USB를 통해 한 번 연결되면 이후 디버그 세션은 네트워크를 통해 실행될 수 있습니다. Wi-Fi 디버깅은 예측 불가능한 지연 시간과 패킷 손실로 인해 CI 서버에 적합하지 않으므로 자동화된 파이프라인은 항상 USB를 사용합니다. 그러나 로컬 개발의 경우 Wi-Fi 디버깅이 훨씬 편리합니다—개발자가 케이블에 묶이지 않고 방 반대편에 있는 기기에서 애플리케이션을 테스트할 수 있습니다.
Android Debug Bridge (ADB)는 명령줄에서 Android 기기와 상호 작용하기 위한 범용 도구입니다. ADB를 통해 애플리케이션을 설치하고, 디버깅을 시작하고, 파일을 복사하고, shell 명령을 실행하고, 로그를 볼 수 있습니다. Android Studio는 모든 디버깅 작업에 ADB를 내부적으로 사용합니다.
Android Studio는 두 가지 디버깅 모드를 지원합니다: Run(일반 실행) 및 Debug(디버거 연결 실행). Debug 모드에서는 편집기에서 직접 중단점을 설정하고, Debug Tool Window에서 변수를 검사하고, Evaluate Expression에서 표현식을 평가할 수 있습니다. 백그라운드 프로세스(Service, BroadcastReceiver)를 디버그하려면 Attach Debugger to Android Process를 사용하십시오.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 여기 중단점이 실행을 일시 중지합니다
val button = findViewById<Button>(R.id.btn_debug)
button.setOnClickListener {
startDebugProcess()
}
}
private fun startDebugProcess() {
val data = fetchDataFromApi()
Log.d("Debug", "Data loaded: $data")
}
}
ADB shell 명령은 루트 권한 없이 기기 파일 시스템에 액세스할 수 있습니다. databases 디렉토리의 내용을 보고, .db 파일을 컴퓨터에 복사하고, SQLite 클라이언트로 열 수 있습니다. Android Studio Database Inspector는 이 프로세스를 자동화합니다: 실시간으로 라이브 데이터베이스 데이터를 보고 IDE에서 직접 SQL 쿼리를 실행할 수 있습니다.
Xcode는 LLDB 기반의 통합 디버깅 환경을 제공합니다. 개발자는 시뮬레이터 또는 실제 기기에서 애플리케이션을 실행하고, 중단점을 설정하고, Debug Navigator를 사용하여 실행 스레드를 제어할 수 있습니다. Android와 달리 iOS는 특별한 구성 없이 동일한 기기에서 두 개의 Debug 빌드를 동시에 실행할 수 없습니다.
시뮬레이터는 애플리케이션을 기본 macOS 프로세스로 실행하여 가장 빠른 디버깅 사이클을 제공합니다. 실제 기기에서는 USB 또는 Wi-Fi(iOS 16부터)를 통해 디버깅이 이루어지며 LLDB는 기기의 debugserver와 통신합니다. USB 2.0의 제한된 대역폭으로 인해 기기에서의 디버깅 성능은 낮지만 실제 기기만이 푸시 알림, 카메라, 센서와 같은 실제 시나리오를 테스트할 수 있습니다.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
let label = UILabel()
label.text = "디버그 모드"
label.textColor = .systemBlue
view.addSubview(label)
}
}
Xcode Organizer는 Crash Logs를 통해 테스터 기기에서 크래시 로그를 수집합니다. symbolication(주소를 함수 이름으로 변환)에는 각 Debug 빌드에서 생성되는 .dSYM 파일이 필요합니다. Release 빌드에서도 dSYM이 생성되지만 App Store의 크래시 로그는 수동으로 또는 bitcode 서비스를 통해 Organizer에 업로드해야 합니다.
자주 묻는 질문
기술적으로 가능합니다—Debug 인증서로 Ad Hoc 배포를 통해, 하지만 Apple과 Google은 이를 권장하지 않습니다. Debug 빌드에는 디버깅 심볼과 낮은 성능이 포함되어 UX를 저하시키고 애플리케이션 크기를 2–3배 증가시킵니다.
이유는 컴파일러 최적화가 비활성화되었기 때문입니다(-O0). 컴파일러는 함수를 인라인화하지 않고, 데드 코드를 제거하지 않으며, 모든 중간 변수를 유지합니다. 또한 Debug에는 Release에는 없는 어설션 검사와 배열 경계 검사가 포함됩니다.
Xcode에서 Window → Devices and Simulators를 선택하고 기기에 대해 “Connect via network”를 체크하세요. 기기와 Mac이 동일한 Wi-Fi 네트워크에 있어야 합니다. USB를 통해 한 번 연결하면 이후 실행에서 디버깅이 Wi-Fi를 통해 작동합니다.
Attach to process는 애플리케이션을 다시 시작하지 않고 이미 실행 중인 프로세스에 디버거를 연결할 수 있습니다. 이는 표준 Debug Run이 적용되지 않는 Service, BroadcastReceiver 또는 시스템 이벤트로 시작되는 프로세스를 디버깅하는 데 유용합니다.
NSLog와 print는 기본적으로 Debug 구성에서만 로그를 출력합니다. Release의 경우 OSLogType.default 플래그와 함께 os_log를 사용하세요— Unified Logging System에 메시지를 저장하며 Mac의 Console.app을 통해 액세스할 수 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.