iOS
디버깅을 위한 Xcode 활용 방법
2022년 3월 21일
원문에서 보기 ↗1. 개요
- iOS SDK 개발과 디버깅을 하던 중 문뜩, 주로
po명령어만 호출하는 자신을 보고 매우 속상했습니다. po명령어만 호출했다는 것 => breakpoint 걸고 변수 출력만 계속 해왔다는 소리인데요..- Xcode를 단지 출력용으로만 사용하기에는 아깝다는 생각이 들었습니다.
- 그래서 디버깅을 위해 Xcode를 좀 더 잘 활용하고자 조사를 했고 내용을 공유드리고자 합니다.
[참고] Xcode 13.2를 기준으로 글을 작성하였습니다.
2. 목차
- LLDB 1.1 po 명령어 1.2 변수 생성
- breakpoint 2.1 breakpoint 생성 및 제거 2.2 Edit Breakpoint 2.3 Column Breakpoint 2.4 watchpoint
- Network 3.1 Network Condition 3.2 Network Instrument
- UI 4.1 UIKit에서 프리뷰 사용하기 4.2 Environment Overrides
- 기타 5.1 Analyzer
3. 내용
1. LLDB
1.1 po 명령어
- 디버깅에서 가장 널리 사용되는 방법입니다.
- breakpoint를 기점으로 변수에 어떤 값이 저장되어 있는지 알 수 있습니다.
- LLDB에서
po {변수}를 입력하면 변수에 대한 description이 출력됩니다. (당연히 property 접근도 가능합니다.)

- 런타임 때 변수값을 직접 값을 수정할 수 있습니다.

1.2 변수 생성
- LLDB에서 런타임 때 직접 변수를 생성하고 사용할 수 있습니다.
- 명령어 :
po var ${변수명} = {value}

2. breakpoint
2.1 breakpoint 생성 및 제거
- 코드 왼쪽 코드 라인을 클릭하면 breakpoint를 생성할 수 있습니다.
- 이렇게 생성한 breakpoint를 line breakpoint라고 부릅니다. 코드 라인 그 자체에 breakpoint를 생성하는 것입니다.
- 생성한 breakpoint를 드래그해서 밖으로 끄집어내면 breakpoint를 제거할 수 있습니다.

2.2 Edit Breakpoint
- 생성한 breakpoint를 더블클릭하면
Edit Breakpoint창이 뜹니다. (오른쪽 클릭 > Edit Breakpoint 선택을 눌러도 동일한 화면이 나옵니다.)

- Name : breakpoint에 네이밍을 지정할 수 있습니다.
- Condition : 특정 조건일 때만 break가 걸립니다. (swift 문법으로 작성합니다.)
- Ignore : 특정 횟수 이후부터 break가 걸립니다.
- Action : break 걸리기 전, 특정 동작을 수행합니다. (LLDB 명령어, scirpt, Sound 등등)

- Automatically continue after evaluating actions : Edit Breakpoint에 설정한 동작은 수행하지만, break가 걸리지 않습니다.
- 예를 들어서 설명해보겠습니다.
- 아래처럼 설정을 해준다면,

If_tripList_count_maximum이란 breakpoint는self.trips.count > 10일 때 Action을 수행합니다.- Action은 디버그 콘솔창에
self.trips를 출력하는 것이고, Automatically continue ...가 체크되었기 때문에 Action 수행 후 break가 걸리지 않습니다.
- 아래처럼 설정을 해준다면,
2.3 Column Breakpoint
- column breakpoint는 line breakpoint와는 다르게 코드 라인 중 특정 부분에 breakpoint를 생성하는 것입니다.
- breakpoint를 걸 곳에 command + 클릭 > Create Column Breakpoint로 생성할 수 있습니다.

2.4 watchpoint
- 어쩔 땐, 변수값이 언제 어디서 바뀌는지 알고 싶을 때가 있습니다. 하지만 로직이 복잡할수록 어디서 변하는지 일일이 체크하기는 쉽지 않습니다.
- watchpoint는 변수값이 수정되는 곳에 일일이 breakpoint를 만들지 않아도 break가 걸리도록 해줍니다.
- 예제를 통해 사용방법을 알아보겠습니다.
- 일단, 트래킹 하고 싶은 변수가 처음 생성되거나 사용하는 곳에 breakpoint를 걸어줍니다. (예제에서는 count라는 변수를 트래킹 해보겠습니다.)

- 앱 실행 후 break가 걸리면 아래 콘솔 왼쪽 화면에서 트래킹할 변수를 오른쪽 클릭하고 "Watch {변수}" 를 클릭해줍니다.

- 이제 변수값이 수정되면, 수정하는 곳에 break가 걸리게 됩니다. (Increase, Decrease 버튼으로 count 변수값을 바꿀 때마다 break가 걸리는 것을 볼 수 있습니다.)

- 일단, 트래킹 하고 싶은 변수가 처음 생성되거나 사용하는 곳에 breakpoint를 걸어줍니다. (예제에서는 count라는 변수를 트래킹 해보겠습니다.)
3. Network
3.1 Network Condition
- 서버 요청/응답이 필요한 앱의 경우 여러 네트워크 상태에 대해서 테스트가 필요합니다. (네트워크가 좋지 않을 경우, 네트워크가 끊겼을 경우 앱의 동작 확인 등)
Xcode > Window > Devices and Simulator > DEVICE CONDITIONS에서 네트워크 상태를 조절할 수 있습니다.

- 네트워크 100% 유실 뿐만 아니라 2G 환경, 3G 환경 등등 다양하게 선택할 수 있습니다.

3.2 Network Instrument
- 서버 요청/응답에 대한 정보 알고 싶을 때 유용합니다. iOS 15 이상 디바이스에서만 확인이 가능합니다. (시뮬레이터는 불가능합니다.)
Instruments앱을 실행하고Network를 선택합니다.

- 녹화 버튼을 눌러줍니다.

- HTTP Traffic에서는 Task Durations, URL Sessions Tasks, HTTP Transactions를 확인할 수 있습니다.
- Task Durations

- URL Sessions Tasks

- HTTP Transactions

4. UI
4.1 UIKit에서 프리뷰 사용하기
- UIKit 기반 앱에서 UI를 구성한 뒤 확인을 하려면 앱을 빌드하고 확인해야 할 뷰까지 타고 들어가야 합니다.
- 또한, 뷰의 구성이 복잡하거나 Constraint가 복잡해지면 뷰가 어떻게 출력될지 직접 확인하지 않고서는 예상하기 어렵습니다.
- 이때, SwiftUI 프리뷰 기능을 활용하면 손쉽게 뷰를 확인할 수 있습니다.
- 예를 들어서, 아래와 같은 뷰가 있을 때
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Label"
label.backgroundColor = .yellow
label.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
label.widthAnchor.constraint(equalToConstant: 100),
label.heightAnchor.constraint(equalToConstant: 50),
])
}
}
- SwiftUI를 import 해주고 프리뷰 관련 코드를 입력해 주면, UIKit 프로젝트에서 뷰가 수정되어도 바로바로 확인이 가능합니다.
- (프리뷰 코드는 Debug 빌드에서만 사용할 수 있도록 전처리해 주시면 더욱 좋습니다.)
import UIKit
import SwiftUI
class ViewController: UIViewController {
...
}
#if DEBUG
struct ViewControllerRepresentation: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ViewController {
return ViewController()
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
}
}
struct ViewController_Previews: PreviewProvider {
static var previews: some View {
ViewControllerRepresentation()
}
}
#endif

4.2 Environment Overrides
- 앱을 만들 때 라이트 모드 & 다크 모드에서 화면이 잘 출력되는지 확인해야 합니다. 또한, 텍스트 크기가 커졌을 때 앱을 정상적으로 이용할 수 있는지도 중요하게 고려해야 합니다.
- Environment Overrides는 앱 빌드 후에 위 설정들을 동적으로 쉽게 바꿀 수 있게 해줍니다.

5. 기타
5.1 Analyzer
Analyzer는 런타임 때 발생할 수 있는 버그를 미리 확인할 수 있습니다.Product > Analyze를 클릭하면 Analyzer 기능을 사용할 수 있습니다.

- Analyzer는 런타임 때 버그가 생길 수 있는 상황들에 대해서 미리 예측하고 시각적으로 보여줍니다.
- 예를 들어서 설명해 보자면, 아래처럼 구현했을 때,
- (nonnull NSString *)returnNonnullString:(CustomEnum)cases {
NSString *result = nil;
switch (cases) {
case Case1:
result = @"this is case 1";
break;
case Case2:
result = @"this is case 2";
break;
default:
break;
}
return [result substringToIndex:5];
}
- method의 리턴값은 non-null이지만 파라미터로 전달받은 cases가 Case1 또는 2가 아닌 경우, null이 return 되게 됩니다. (리턴 타입이 서로 다릅니다.)
- 이때, Analzyer를 사용하면 버그 상황을 시각적으로 보여줘서 빠르게 캐치할 수 있습니다.

- 런타임 버그는 눈에 잘 띄지 않기 때문에, Analyzer를 이용하면 좀 더 안전한 코드를 만들 수 있게 됩니다.
- 위와 같은 버그뿐만 아니라 무한 루프, Asserts의 side effect 등 매년 검사 항목들이 추가되고 있으니 잘 활용하면 도움이 될 것 같습니다.
매 빌드 때마다 Analyze를 따로 호출할 필요 없이,
Build Settings > Analyze During 'Build' = YES로 설정하면 빌드 할 때 알아서 Analyzer가 동작합니다.
4. 결론
- Xcode를 더 잘 활용하고 싶으신 분들에게 도움이 되었으면 하는 바람으로 글을 적어보았습니다.
- 제가 실질적으로 개발할 때 사용하고 있는 & 사용할 것 같은 디버깅 방법들 위주로 설명을 하느라 내용이 많이 생략되었는데요, 위에서 소개한 방법 외에도 Symbolic Breakpoint 라던가 LLDB에서 python script 실행 등 더 다양한 디버깅 방법들이 있으니 관심이 있으시면 LLDB에 대해 좀 더 찾아보시는 것도 추천드립니다.
- 긴 글 읽어주셔서 감사합니다.
