제목: System Level Breakpoints in Swift


모든 훌륭한 소프트웨어 개발자들은 결국 훌륭한 소프트웨어 디버거가 되야한다. 디버깅은 크게는 브레이크포인트의 설정으로 이루어지고, 실행시간동안 앱의 임의의 상태 지점을 관찰하기위해 브레이크 포인트를 걸어 놓은 곳에 가본다. 큼직하게는 두가지 종류의 브레이크 포인트가 있다. 하나는 당신의 코드에 설정한 것이고, 다른 하나는 다른사람의 코드에 설정한 것이다.

여러분의 코드에 브레이크 포인트를 설정하는것은 간단하다. 그냥 Xcode 프로젝트의 소스코드 라인을 찾아서, 관련된 라인 옆에 홈 안의 공간을 탭하면 된다.



그러나 시스템 API에대해 브레이크 포인트를 설정하고 싶거나 소스코드를 가지고 있지 않은 라이브러리 안에 구현된 메소드에 브레이크 포인트를 설정하고 싶은 경우는 어떨까? 예를들어, 레이아웃 버그를 잡아야한다고 할때, UIView에서 애플의 고유의 내부 layoutSubviews 메소드 호출을 관찰하는데 도움이 될 수 있다. 역사적으로 Objective-C 개발자들에게는 이것이 큰 문제가 아니다. 어떤 메소드를 심볼릭하게 표현하기위해 그리고 그것을 브레이크하기위해, Xcode의 lldb 콘솔(View -> Debug Area -> Activate Console)로 들어가서 이 이름을 지정해가면서 브레이크 포인트를 지정하면 된다는 것을 안다. lldb에서 "b"라는 단축 명령어는 우리가 입력한 것으로부터 전체 이름과 매칭되는 것을 찾는 정규식이다.

(lldb) b -[UIView layoutSubviews]
Breakpoint 3: where = UIKit`-[UIView(Hierarchy) layoutSubviews], address = 0x000000010c02f642(lldb)

lldb 콘솔에서 겁을 주거나, 현재 디버그 세션보다 더 길게 붙이는 브레이크 포인트를 원하면, Xcode의 내장된 심볼릭 브레이크 포인트 인터페이스(Debug -> Breakpoints -> Create Symbolic Breakpoint)를 사용하여 같은 결과를 달성할 수 있다.


사실, 여러분의 iOS 앱에 브레이크 포인트를 걸고 앱을 돌리면 애플의 layoutSubviews 메소드에서 브레이크 포인트 안으로 실행시켜볼것이라고 생각한다 lldb 콘솔로 돌아와서 메시지를 받은 오브젝트를 확인한다.

(lldb) po $arg1
<UIClassicWindow: 0x7f8e7dd06660; frame = (0 0; 414 736); userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x60000004b7c0>; layer = <UIWindowLayer: 0x600000024260>>

이제 계속해서 심블에 브레이크를 걸어보자. 그리고 또다시 그렇게 하자. lldb 콘솔에 "po $arg1"이라고 입력하여 매번 타겟을 확인하라. 유별난 버그를 추적하는동안 이런 종류의 분석을 시행하는게 얼마나 유용한지 상상할 수 있을 것이다.

그러나 우리 플랫폼을 이제 시작한 가여운 스위프트 프로그래머들이나 스위프트 문법에만 열관하는 사람들은 어떻게할까? 애플의 문서를 읽어보거나, "-[UIView layoutSubviews]"를 해석하는게 불가능한 사람들은 "UIView.layoutSubviews"이 완전히 눈에 거슬릴 뿐만 아니라, 스위프트에게는 옳은 것일까?

불행히도 "UIView.layoutSubviews"라고 브레이크포인트를 설정하면 동작하지 않는다.

(lldb) b UIView.layoutSubviews
Breakpoint 3: no locations (pending).WARNING:  Unable to resolve breakpoint to any actual locations.
(lldb)

이것이 실패하는 이유는, UIView에 스위프트로 구현된 layoutSubviews라는 메소드가 없기 때문이다. 이것은 모두 Objective-C로 구현되있다. 사실 아주 많은 스위프트에서 쓸 수 있는 Objective-C 메소드들이 Objective-C 메시지 전송으로 바로 전달하도록 컴파일된다. 스위프트 파일에 "UIView().layoutIfNeeded()"같은 것을 입력하면, 컴파일은 될것이며, layoutIfNeeded라는 스위프트 메소드가 없기때문에 호출해도 아무일도 일어나지 않을 것이다.

이것이 스위프트로 맵핑된 모든 Cocoa 타입들에게 해당되지는 않는다. 예를들어, 모든 "Data.write(to:options:)" 호출에 브레이크를 걸고 싶다고 생각하자. 아마 "Data.write"에 브레이크 포인트를 걸어서 동작하길 원할것이다.

(lldb) b Data.write
Breakpoint 11: where = libswiftFoundation.dylib`Foundation.Data.write (to : Foundation.URL, options : __ObjC.NSData.WritingOptions) throws -> (), address = 0x00000001044edf10

그리고 된다! 이건 어떨까? 실제로 이것만 아니다. 이것은 -[NSData writeToURL:options:error:]의 길에서 libswiftFoundation을 통해 전달하는 모든 호출에 브레이크가 걸릴 것인데, Objective-C 구현을 호출하는것에 직접 잡히지는 않을 것이다. 메소드에서 모든 호출을 잡기 위해서는, 저수준(Objective-C 메소드)에서 브레이크포인트를 설정할 필요가 있다.

따라서 규칙으로, iOS나 Mac 플랫폼에서 더 좋은 디버거를 원하는 스위프트 프로그래머들은 Objective-C의것과 동일하게 스위프트 메소드를 매핑할 수 있는 능력이 필요하다. UIView.layoutSubviews같은 메소드의경우, 바로 "-[UIView layoutSubviews]"로 맵핑하지만, 많은 메소드의경우 지금처럼 간단하게 될것이다.

스위프트로 맵핑된 메소드 이름을 Objective-C로 다시 매핑하기위해서는, 많은 Foundation 클래스들이 NS 접두를 뺐다는 것을 인식하고, 스위프트 API 가이드라인을 적용하기위해 메소드 시그니처를 다시 작성한 영향이다. 예를들어 순수한 스위프트 프로그래머들은 "Data.write(to:options)"의 저수준 구현으로 브레이크 포인트를 설정하기 때문에 쉽게 유추하지 못할것이다. "NS" 접두를 붙이고, URL 파라미터를 명시적으로 넣고, 이상한 에러 파라미터를 추가한다. 이것은 보기에 옛날의 나쁜 시기에 까탈스러운 백발의 노인이 실패를 전달하는데 사용하는 것 같아 보인다.

(lldb) b -[NSData writeToURL:options:error:]
Breakpoint 13: where = Foundation`-[NSData(NSData) writeToURL:options:error:], address = 0x00000001018328c3

성공했다!

이런 Obejctive-C 메시지 시그니처와 API 규약들에대한 추가적인 지식을 개발하게 만드는 생각은 여러분을 힘겹게 만든다는 점에서, 나는 여러분의 다음 과제를 통해 얻을 약간의 수정된것을 제공한다(I offer a little hack that will likely get you through your next challenge). API가 이런 원리중 하나를 사용햐여 작성되었으면, 함수의 스위프트 이름은 거의 Objective-C 메소드 이름의 부분집합이다. 아마도 lldb의 정규식 일치 기능을 활용하여 브레이크포인트를 설정하고싶은 메소드를 0으로 만들 수 있다.

(lldb) break set -s Foundation -r Data.*write
Breakpoint 17: 8 locations.

이제 "break list"를 입력하고 lldb가 표시한 매칭 수를 보자. 그것들 중에는 스위프트로 된 (libswiftFoundation으로 이루어진) 여러 메소드가 있으나, 질문에서 타겟 메소드를 찾아야할 것이다. 사실, 여러분이 브레이크를 걸고싶은 다른 저수준의 Objective-C 메소드도 찾아봐야할 것이다.

리스트를 더 잘 관리하기위해, 주어진 Objective-C 프레임워크안에 타겟 메소드가 있다는 지식을 주고, 이름으로 특정 공유된 라이브러리에 제한된 매칭을위한 "-s" 플래그를 넣는다.

(lldb) break set -s Foundation -r Data.*write
Breakpoint 17: 8 locations.

이 브레이크포인트 사이에서 NSPageData에서 몇 거짓이 있지만, 그 목록은 모두 더 관리할 수 있다. 하나의 브레이크포인트 "17"은 하위-숫자로 식별된 매치의 모든것을 가지고 있는다. 여러분의 방법으로 브레이크포인트의 목록을 걸러내도 좋다.

(lldb) break disable 17.6 17.7 17.8
3 breakpoints disabled.
(lldb) c

Objective-C를 스위프트에 맵핑하는 애플의 방식은 스위프트 개발자를위해 더 즐거운 프로그래밍 경험을 함께 만들게 해주지만, 그 세부적인 구현을 이해하지 못하고 있거나 어떻게 동작하는지에대한 이해가 부족하게되면 엄청난 혼란으로 빠질 수 있다. 나는 이 글이 여러분의 스위프트 앱을 디버깅하는데 필요한 도구가 되었으면 좋겠고, 불가피하게 사용하는 Objective-C 코드를 더 효율적이게 썼으면 좋겠다.

Update: 나는 두가지 관련된 버그를 넣었다: Radar #31115822 스위프트 메소드 포맷에서 자동으로 매핑한 것을 Obejctive-C 메소드로 돌아오도록 해야한다, 그리고 Radar #31115942 간결한 스위프트 메소드 시그니처를 만드는것에대해 lldb를 더 직관적으로 만들도록 해야한다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,