- swift 和 oc 的語法不一樣:
Xcode 調試技巧之 Swift 篇
打印和賦值,觀察數值變量和view對象屬性
- p指令可打印其對象類型、內存地址以及該對象的值等具體信息,
- po指令則是打印其調用description方法得到的值。
- e 賦值指令(后面有例子詳解)
流程控制
n 命令,代表 Step Over 操作。
s 命令,代表 Step Into 操作。
finish 命令,代表 Step Out 操作。
c 命令,代表恢復程序執行操作。
修改指針變量的值,觀測程序不同變化
程序中: testThreadReturn = “testThreadReturn”
(lldb) p testThreadReturn
(String) $R2 = "testTheardReturn"
(lldb) e testThreadReturn = "zaozuo"
(lldb) p testThreadReturn
(String) $R4 = "zaozuo"
動態修改view的屬性
Thread Return,函數設置斷點返回值(swift不兼容)
- 這個在OC一切ok,在swift還不能用會有以下錯誤
(lldb) thread return "zaozuo-return"
error: Error returning from frame 0 of thread 1: We only support setting simple integer and float return types at present..
調試時,還有一個很棒的函數可以用來控制程序流程:thread return 。它有一個可選參數,在執行時它會把可選參數加載進返回寄存器里,然后立刻執行返回命令,跳出當前棧幀。這意味這函數剩余的部分不會被執行。這會給 ARC 的引用計數造成一些問題,或者會使函數內的清理部分失效。但是在函數的開頭執行這個命令,是個非常好的隔離這個函數,偽造返回值的方式 。
直接設置返回值,不用寫死代碼破壞代碼結構
(五星推薦):編輯斷點
設置斷點進入條件,condition
斷點行為 (Action):
可以設置或不設置,斷點進入的條件,設置進入條件后還可以,自定義后續的動作,比如,打印值,修改值,執行shell命令,執行lldb命令,打印log等等很多自定義的動作,這一點確實很強大,這里就可以和shell 腳本聯動,測試一些不好測試的點,和需要提前處理外部因素的案例
(五星推薦):在lldb中初始化值,動態賦值,執行多行語句
- 簡單變量賦值,僅僅在調試環境賦值了一個值,是局部變量,可以用print打印
(lldb) e let hello = "Hello"
you can simply use:
(lldb) p let hello = "Hello"
- 使用"$"符號定義全局常量和變量
OC中為:
(lldb) e NSString *$a = @"c" (lldb) po $a c
Swift中變為:
(lldb) e let $a = "a"
(lldb) po $a "a"
- Swift中多行語句:輸入完成敲一個空行即可開始執行
(lldb) p
Enter expressions, then terminate with an empty line to evaluate:
struct compass{var direction = "N"; var angle = 16.5}
var c = compass()
print(c)
(lldb)
- 同樣的你也可以向當前view添加一個layer
(lldb) p
Enter expressions, then terminate with an empty line to evaluate:
1 let layer = CALayer()
2 layer.backgroundColor = UIColor.yellow.cgColor
3 layer.bounds = CGRect(x:0, y:0, width:100, height:100)
4 layer.position = CGPoint(x:250, y:300)
5 layer.cornerRadius = 12.0
6 view.layer.addSublayer(layer)
7
(lldb)
- 修改函數中成員變量的值
引入模塊 Importing modules
(lldb) p import MapKit
打印UI層級關系及更新UI
- oc,試了下這個在swift也可以運行:
(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x7ffcd2f0f1e0; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7ffcd2f10170>; layer = <UIWindowLayer: 0x7ffcd2f0ea80>>
| <UIView: 0x7ffcd2c6dc10; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7ffcd2c17f10>>
| | <UIButton: 0x7ffcd2c6dfc0; frame = (20 62; 78 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7ffcd2c6bc10>>
| | | <UIButtonLabel: 0x7ffcd2f15af0; frame = (16 6; 46 18); text = 'Button'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7ffcd2f16120>>
| | <_UILayoutGuide: 0x7ffcd2c6fae0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7ffcd2c6faa0>>
| | <_UILayoutGuide: 0x7ffcd2c70740; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x7ffcd2c6d3d0>>
- swift:
expr -l objc++ -O -- [[[UIWindow keyWindow] rootViewController] _printHierarchy]
- view指針賦值
(lldb) e id $view = (id) 0x7fbd71432590
- 使用指針進行操作
(lldb) e (void) [$view setBackgroundColor:[UIColor redColor]]
- 當然,我們運行完這條命令,界面上不會馬上反應出來,我們還需要
調用這個命令刷新一下:
(lldb) e (void)[CATransaction flush]
Push 一個 View Controller
- 第一步我本地實驗了下沒有成功,報錯了就,可能我代碼是swift的所以得用swift 代碼,下面僅僅oc 可以
(lldb) e navigationController?.pushViewController($vcc, animated: true)
MainNavigationController.swift[36], pushViewController: [pushViewController=====><zaozuo.ProductCategoryController: 0x7ffce5147600>]
(Swift.Void?) $R4 = nil
然后執行:
(lldb) caflush // e (void)[CATransaction flush]
或是
(lldb) c
都可以
查找按鈕的 target(這個方法還未嘗試,留在這里開闊下思路)
想象你在調試器中有一個 $myButton 的變量,可以是創建出來的,也可以是從 UI 上抓取出來的,或者是你停止在斷點時的一個局部變量。你想知道,按鈕按下的時候誰會接收到按鈕發出的 action。非常簡單:
(lldb) po [$myButton allTargets]
{(
<MagicEventListener: 0x7fb58bd2e240>
)}
(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
<__NSArrayM 0x7fb58bd2aa40>(
_handleTap:
)
觀察實例變量的變化
假設你有一個 UIView,不知道為什么它的 _layer 實例變量被重寫了 (糟糕)。因為有可能並不涉及到方法,我們不能使用符號斷點。相反的,我們想監視什么時候這個地址被寫入。
首先,我們需要找到 _layer 這個變量在對象上的相對位置:
(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class], "_layer"))
(ptrdiff_t) $0 = 8
現在我們知道 ($myView + 8) 是被寫入的內存地址:
(lldb) watchpoint set expression -- (int *)$myView + 8
Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w
new value: 0x0000000000000000
這被以 wivar $myView _layer 加入到 Chisel 中。
非重寫方法的符號斷點
假設你想知道 -[MyViewController viewDidAppear:] 什么時候被調用。如果這個方法並沒有在MyViewController 中實現,而是在其父類中實現的,該怎么辦呢?試着設置一個斷點,會出現以下結果:
(lldb) b -[MyViewController viewDidAppear:]
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.
因為 LLDB 會查找一個符號,但是實際在這個類上卻找不到,所以斷點也永遠不會觸發。你需要做的是為斷點設置一個條件 [self isKindOfClass:[MyViewController class]],然后把斷點放在 UIViewController 上。正常情況下這樣設置一個條件可以正常工作。但是這里不會,因為我們沒有父類的實現。
viewDidAppear: 是蘋果實現的方法,因此沒有它的符號;在方法內沒有 self 。如果想在符號斷點上使用 self,你必須知道它在哪里 (它可能在寄存器上,也可能在棧上;在 x86 上,你可以在 $esp+4 找到它)。但是這是很痛苦的,因為現在你必須至少知道四種體系結構 (x86,x86-64,armv7,armv64)。想象你需要花多少時間去學習命令集以及它們每一個的調用約定,然后正確的寫一個在你的超類上設置斷點並且條件正確的命令。
幸運的是,這個在 Chisel 被解決了。這被成為
bmessage:
(lldb) bmessage -[MyViewController viewDidAppear:]
Setting a breakpoint at -[UIViewController viewDidAppear:] with condition (void*)object_getClass((id)$rdi) == 0x000000010e2f4d28
Breakpoint 1: where = UIKit`-[UIViewController viewDidAppear:], address = 0x000000010e11533c
讓代碼停在Swift Error 或者Objective C異常
- 停在Objective C異常
(lldb) br s -E objc
Breakpoint 6: where = libobjc.A.dylib`objc_exception_throw, address = 0x000000010dededbb
- 停在Swift Error
(lldb) br s -E swift
Breakpoint 7: where = libswiftCore.dylib`swift_willThrow, address = 0x000000010e55ccc0
- 停在某一種類型的Swift Error
(lldb) br s -E swift -O EnumError
Breakpoint 8: where = libswiftCore.dylib`swift_willThrow, address = 0x000000010e55
參考
http://lldb.llvm.org/
https://lldb.llvm.org/lldb-gdb.html
https://www.invasivecode.com/weblog/lldb-expression-for-code-injection-in-xcode
http://www.theappbusiness.com/blog/debugging-in-swift
iOS 開發者旅途中的指南針 - LLDB 調試技術,swiftcafe
與調試器共舞 - LLDB 的華爾茲
Chisel-LLDB命令插件,讓調試更Easy
Chisel常用命令總結
Swift 代碼調試核武-LLDB調試基礎
Basic LLDB tips
udacity 的視頻教程
結尾
LLDB 調試命令強大,但是有些語法不大好書寫,Chisel 做了封裝和一些擴展,使得調試App 不在打印各種log,我們可以快捷的查看或是修改代碼內的變量,常量,數組,對象,在斷點位置執行log,shell等但這都是調試代碼內部信息,如何更加靈活的調試UIView和約束信息呢?
這就有了下文: