用户
 找回密码
 立即注册

发帖

iOS开发中一些Debug方法

[复制链接]
  • TA的每日心情
    慵懒
    2025-11-24 10:46
  • 27

    主题

    6

    回帖

    669

    积分

    管理员

    积分
    669
    发表于 2015-5-22 00:00:00
    当时刚开始搞iOS开发的时候,对断点调试的理解局限于:看代码风骚的走位,即当运行出来的效果对不住我的代码时,我会去看代码是怎么运行的,然后用雍正之剑去砍杀八阿哥。后面慢慢的接触lldb后,发现lldb用起来真的很赞,下面我来分享一下我在平时开发中积累的一些用法,这些用法有些是从网上发现的,有些是同事告诉我的。
    相关用法
    打印网络请求的相关信息
    现在开发项目中没有网络请求都不好意思说自己在搞项目了,那为了方便调试,是不是要把请求的网址、请求的参数、服务器返回的数据等相关打印出来呢?我以前的做法都是在每个请求的API方法里将这些信息NSLog打印出来,直到最近我的主管告诉了我一个秘密。在网络请求库收到data的方法里面添加Debugger Command类型的断点。
    1. po [connection.currentRequest.URL absoluteString]0 y2 P. Y4 X8 h* F' j3 e4 i& d; T
    2. po [NSString stringWithCString:(char*)[connection.currentRequest.HTTPBody bytes] encoding:4]
      + |+ q& s* \  o9 F5 K
    3. po [NSString stringWithCString:(char*)[data bytes] encoding:4]
    复制代码
    其实上面打印的东西我们都知道,我只是把他们放在了一个断点里面,让可以进行三连击:1、输出请求网址;2、输出请求参数;3、输出返回数据。
    这样不仅解决了NSLog打印出来的中文数据显示为UTF8格式,还能省下我们很多代码量,是不是很nice。。。
    注:AF接收数据的方法 在AFURLConnectionOperation文件
    1. - (void)connection:(NSURLConnection __unused *)connection* T8 E! F. a, q# L4 G/ N6 [1 `
    2.     didReceiveData:(NSData *)data)
    复制代码

    显示图片
    当你创建UIImage对象从服务器端获取数据时,假如中间出了一点小问题,为了验证图片是否创建成功,可以试一下下面的debug方法。
    用将鼠标放在UIImage对象上面,然后按option键,在出现的弹出视图上面,点击像眼睛一样的图标,你可以看到:
    005IevIrgw1etip1fj483j30oj0adduk.jpg Image1
    那么图片就显示出来了,并且你还可以选择 Open With Preview 在预览中打开该图片。当然还有一种方法能做到,在控制台的左侧,当断点执行时,你会看到相关对象,选择你要查看的对象,现在我也要查看UIImage对象,然后按space键,也能将图片显示出来。
    005IevIrgw1etip1km9q3j30mb0bfwth.jpg Image2
    刚刚上面说的两种方法,除了能显示UIImage对象以后,UIImageView、UIView也行,至于其他的类型,我以前没有操作过,在以后的实践中可以试试。

    po 的一个隐藏指令
    在lldb中,po (print object的缩写) 是打印某个对象的指令,自然的 po xxxx (某个对象)应该是我用的最多的指令了,其实还有一个指令用来查看某个View的层级结构关系 po [self.view recursiveDescription]。或许你觉得在控制栏下输出一大串代码不是很直观,那么你可以试试Xcode6 出来的 View debug方法,它能很好的解决这个问题,能更直观的查看视图的层级结构。

    改变某些值
    有的时候,我们程序运行时改变某个值,来看看效果。我们可以用exp(expression的缩写)。
    改变某个字符串的值?
    1. (lldb) expression NSString *$xxString = @"111"
      - G: L) P' o1 {# v
    2. (lldb) po $xxString
      0 T% s2 ~( i2 N, c5 g! [# L
    3. 111: e! O6 g# Z6 n' w+ }

    4. 6 C3 n( i- j  P
    5. (lldb) po $xxString = @"2222"0 k5 V2 t3 Y+ f% D* \
    6. 2222% D9 y1 f9 |1 R; b' t2 j5 M) D

    7. : R1 r( V# T/ m  B
    8. (lldb) expression $xxString = @"3333"
      1 d7 R6 t; j- q# Q
    9. (__NSCFString *) $2 = 0x00007fc793675a10 @"3333"
      2 K; J4 N/ h& O  k3 W$ C/ i- d) \3 T
    10. (lldb) po $xxString
      : U( F$ O& w1 [0 h9 ~
    11. 3333
      ( Y& p6 n# H% _* c: V9 x
    12. 2 D1 r( G  n2 D& v% t* i" D
    13. (lldb)
    复制代码
    哦哦,对了 exp Class *$instance是在lldb中创建实例。
    1. (lldb) expression int $b = 108 |; Z4 x" B1 |6 p. u6 f1 _
    2. (lldb) po $b+ L6 c- p! D  f( f, V: e
    3. 105 o* D3 a5 C9 K, n

    4. 5 D6 q" t  ]2 {1 I1 j7 Z2 @* z
    5. (lldb) exp $b = 100: N3 z; [' K% E+ f+ y
    6. (int) $0 = 100, Q5 I  J6 C! W
    7. (lldb) po $b( R! h$ F' F  n9 D
    8. 1003 T- Q2 G8 m. O) J1 \! {
    9. ' n0 e+ }8 ~8 g1 J
    10. (lldb)
    复制代码
    当然,我们还可以改变某个对象的属性,比如,如果我想要改变一个视图的背景颜色。
    1. po self.view.backgroundColor = [UIColor redColor], l% n' \& g' z* M, e. p* y: b6 `8 {
    2. call imageView.backgroundColor = [UIColor greenColor]
    复制代码
    有时候,我想需要改变某个函数的返回值,来测试函数的稳健性,那可以试试 thread return XXX指令。 看一下下面的代码吧。
    1. /// Value
      ; f7 T# [$ x  V7 {9 d) ]; Y* k. G
    2. NSString *string = [self exeGetReturnString];- m1 z0 Z9 I+ {' X! O
    3. 1 ?% G3 Y% O- R7 {- t! E
    4. /// Method* ]6 f+ }# c2 C6 H% ~7 O
    5. - (NSString *)exeGetReturnString {
      $ d/ t* S/ ^/ r+ l1 ~# U
    6. * f# I6 |" g+ s/ k6 Z7 ^( S
    7.     return @"1111";
      - b1 H) Y: S, p
    8. }" N0 ?) D3 }+ V2 I( e0 H' q

    9. 5 V. g7 L# t9 Z  a
    10. /// lldb
      ; W: x2 P4 `/ u& i9 s/ b) }' p( B2 I" }
    11. (lldb) thread return @"This is Changed String!!!"
      ( V6 m. E& \; R% c
    12. (lldb) po string4 g7 x; f6 a! _
    13. This is Changed String!!!
      & ~4 s7 J2 N5 t
    14. % e! B: A" V/ ?* }" b9 A. a. v& V
    15. (lldb)
    复制代码
    我把断点设置在函数exeGetReturnString的返回值前面,可以看出string的值变了。这些改变值的方法,有时在调试程序的时候是非常有用的。
    指定输出的格式
    对于基本简单类型,我们可以指定它的输出类型p/x(x表示输出的类型),这种最能体现在的就是整数类型数据之间二进制、八进制、十进制、十六进制的转换,虽然我们能算出来,但电脑应该算的比我们快,比我们准确吧!好吧,那让我们来一发试试吧。
    1. (lldb) expression int $a = 1
      " ^5 }4 v9 D" Z. D/ f9 H
    2. (lldb) po $a
      - {1 r5 @5 h. ]# N: n8 m
    3. 1" i$ I9 F) a: e1 b4 s5 Z

    4. 1 V8 f, U2 \; z' C
    5. (lldb) p/t $a% `/ Y" D# f) p7 L6 Q$ R8 r! y% `3 q
    6. (int) $a = 0b000000000000000000000000000000019 }' b1 e+ o* B2 E) U; x9 R, Z
    7. (lldb) p/o $a* R  [% u* z& q
    8. (int) $a = 01' K. X; p: m% A6 G; Z0 X
    9. (lldb) p/x $a. d; J7 U6 \( f0 t+ x' {+ K( g8 D
    10. (int) $a = 0x00000001
      1 o( G6 C3 I3 i0 ?7 t+ W
    11. (lldb) p/d 0b00000000000000000000000000000001! E5 _: L/ P* x2 J/ r5 K1 X
    12. (int) $0 = 1' K" C' y( G$ ~0 _( m! ]5 j; N! Q
    13. (lldb) p/d 01
      4 A: S1 H& o% A* _8 _
    14. (int) $1 = 1
      - d+ \2 v3 r: z# @- e
    15. (lldb) p/d 0x00000001
      4 c! c7 S# Q( t4 Q! R) w4 M
    16. (int) $2 = 11 v1 Z, k$ G5 Y
    17. (lldb)
    复制代码

    / |: e5 }  \- O- }7 l4 S$ c9 h! s% l
    上面用的p/X(X代表进制,t,二进制;o,八进制;d,十进制;x,十六进制)。
    上面输出的$X(0-2),这是啥意思呢?其实可以将他们看作是对操作对象的一个引用,可以直接使用这个符号来操作相对应的对象。
    1. (lldb) expression int $a = 10 o+ y3 Y% R6 }  D
    2. (lldb) po $a
        P7 ]5 ~8 V% Z1 ^, f  G
    3. 1- [# v. C9 Z2 Z
    4. ! q, ]9 m3 X- {% P
    5. (lldb) p/t $a& `. C9 `# t* B1 ?+ o( X/ j
    6. (int) $a = 0b00000000000000000000000000000001
      4 _+ `, m" D) U" R4 e. F! z( e- V
    7. (lldb) p/o $a) T* p% |. _& C. ^  T
    8. (int) $a = 01
      " F) m- d' {, s* T1 P
    9. (lldb) p/x $a0 X5 K  Z8 I7 W' e. F" R
    10. (int) $a = 0x000000015 G0 G. C5 F1 K4 y- _9 a
    11. (lldb) p/d 0b00000000000000000000000000000001
      % b$ [% w! m& g. h) }& r
    12. (int) $0 = 1* h. t) n, G8 F; A; D
    13. (lldb) p/d 01
      5 z3 V6 z6 f  o7 e$ T# `  U
    14. (int) $1 = 1* k( P% v% A6 c: Z- X, P
    15. (lldb) p/d 0x000000016 y4 t2 n( m- ^/ X! q2 g
    16. (int) $2 = 19 _6 @' z3 T& e2 u
    17. (lldb)
    复制代码
    查找奔溃信息的位置
    程序崩溃了要咋办?找到崩溃的地方,解决它撒。我们可以添加一个全局的断点Add Exception Breakpoint,当程序崩溃的时候,它能定位错误的代码位置。但是还有一种方法能找到错误的位置。image lookup --address 0xXXXXXXXX(0xXXXXXXXX为程序奔溃时控制台给出的地址)。
    1. NSString *originalString = @"0123";
      ! z" h* n; N* W! _0 w7 L
    2. NSString *subString = [originalString substringFromIndex:5];
    复制代码
    好吧,这个代码的问题比较简单,我只是举个例子哈,[emoji]。
    1. 2015-05-17 23:26:04.790 DebugMethod[5282:1628084] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSCFConstantString substringFromIndex:]: Index 5 out of bounds; string length 4'! Y! E% d" y& r: }: H5 b  Q0 q7 C$ q
    2. *** First throw call stack:; I  n! h8 W7 M1 Z- X- {
    3. (1 m* `4 e" i" `, L
    4.         0   CoreFoundation                      0x0000000104a70c65 __exceptionPreprocess + 165
      - e7 f# }9 [( R/ O6 E
    5.         1   libobjc.A.dylib                     0x0000000104707bb7 objc_exception_throw + 45& }( H6 Z( g/ P- l
    6.         2   CoreFoundation                      0x0000000104a70b9d +[NSException raise:format:] + 205
      4 M. h3 \$ z# l& m" V, S
    7.         3   Foundation                          0x0000000104276ca8 -[NSString substringFromIndex:] + 118
      , i" T7 t' r' b! E! ^) h  ~+ }
    8.         4   DebugMethod                         0x00000001041d3936 -[ViewController viewDidLoad] + 7103 w2 ?8 k! I) Q+ t( x7 J
    9.         5   UIKit                               0x0000000104f9b210 -[UIViewController loadViewIfRequired] + 738
      5 R( b5 H+ o: Z" J4 d+ W
    10.         6   UIKit                               0x0000000104f9b40e -[UIViewController view] + 27; e& _1 f+ T  r' b& p; y/ d
    11.         7   UIKit                               0x0000000104eb62c9 -[UIWindow addRootViewControllerViewIfPossible] + 58
      * _$ l# o9 j6 A+ q
    12.         8   UIKit                               0x0000000104eb668f -[UIWindow _setHidden:forced:] + 247
      / O; y- t6 |/ H( E' D
    13.         9   UIKit                               0x0000000104ec2e21 -[UIWindow makeKeyAndVisible] + 42
      ; \* _" o. M5 D+ V- `; ^
    14.         10  UIKit                               0x0000000104e66457 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2732" U0 t% }: z+ b5 Y$ l) P
    15.         11  UIKit                               0x0000000104e691de -[UIApplication _runWithMainScene:transitionContext:completion:] + 1349' k3 z8 H& ]3 k5 o/ u
    16.         12  UIKit                               0x0000000104e680d5 -[UIApplication workspaceDidEndTransaction:] + 179% N- j" G7 T3 [$ e2 I4 H" ~* L
    17.         13  FrontBoardServices                  0x000000010766f5e5 __31-[FBSSerialQueue performAsync:]_block_invoke_2 + 21" a9 P4 W. V( o: |
    18.         14  CoreFoundation                      0x00000001049a441c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 126 i3 W( S5 B. O  f, J* q
    19.         15  CoreFoundation                      0x000000010499a165 __CFRunLoopDoBlocks + 341) F1 @" h- Y' t
    20.         16  CoreFoundation                      0x0000000104999f25 __CFRunLoopRun + 2389
      3 a* r0 D( S* o; ~2 I
    21.         17  CoreFoundation                      0x0000000104999366 CFRunLoopRunSpecific + 470/ ~$ q* }4 @/ A% ?$ H3 W% E  q
    22.         18  UIKit                               0x0000000104e67b42 -[UIApplication _run] + 413
      " F( s& V* n( j2 x# `
    23.         19  UIKit                               0x0000000104e6a900 UIApplicationMain + 1282- U9 W. {- u" T" R4 Y5 g
    24.         20  DebugMethod                         0x00000001041d3d3f main + 1112 c4 z+ i3 R" d  `
    25.         21  libdyld.dylib                       0x000000010703f145 start + 1- T1 p( n- [6 T( m* K; X
    26. )
      2 U; z0 l1 n4 F* O) v
    27. libc++abi.dylib: terminating with uncaught exception of type NSException
    复制代码
    其实从上面的崩溃信息中,我们可以看出是越界NSRangeException的问题,原因是'*** -[__NSCFConstantString substringFromIndex:]: Index 5 out of bounds; string length 4',字符串本来长度为4,而要取的位置为5,所以越界了!!!
    假如,如果你一个ViewController里面用到了多个[NSString substringFromIndex:]方法,那是不是比较难找到崩溃的地方?这样image lookup --address 0xXXXXXXXX就派上用场了,从上面的错误信息中,可以初步断定viewDidLoad方法里的substringFromIndex:方法出错了,那我们就拿viewDidLoad的里面的错误地址取寻找。
    1. (lldb) image lookup --address 0x00000001041d3936
      : y7 s  |% B; w! N. E
    2.       Address: DebugMethod[0x0000000100001936] (DebugMethod.__TEXT.__text + 710)! Y" s- `( Z8 u/ m2 {( C& {
    3.       Summary: DebugMethod`-[ViewController viewDidLoad] + 710 at ViewController.m:34
      4 Q3 O, H+ b9 B+ |3 W7 R/ u1 x
    4. (lldb)
    复制代码
    结果还真的被猜中了,从上面的结果来看,代码错在ViewController.m文件viewDidLoad34行。
    一些问题的处理
    在使用以上一些方法的时候,也遇到了一些问题。有的时候,它找不到对象类型或者方法,比如下面这个:
    1. (lldb) po self.view.frame
      ' ^! f) C* |, V5 N5 T
    2. error: property 'frame' not found on object of type 'UIView *'. A9 }. w  `0 a
    3. error: 1 errors parsing expression# s* @5 u' ?3 A: }
    4. (lldb) po (CGRect)self.view.frame
      * v; p$ R: j6 S- B) `
    5. error: 'CGRect' is not a valid command.3 s5 e, ~! T* i: X  X4 _1 I; @
    6. (lldb) po (CGRect)[self.view frame]( C! N( [& c& e. X
    7. (origin = (x = 0, y = 0), size = (width = 375, height = 667))3 h' v6 F3 o: j) y3 b! @; J
    8. (origin = (x = 0, y = 0), size = (width = 375, height = 667))
      1 R4 y$ z  D8 S8 j* D+ J
    9. (lldb) exp @import UIKit$ A* c7 A- t/ p: i* z: u
    10. (lldb) po self.view.frame
      % I9 k' T: @0 f; [. e
    11. (origin = (x = 0, y = 0), size = (width = 375, height = 667))
      3 T! v. q5 ?% ?
    12. (origin = (x = 0, y = 0), size = (width = 375, height = 667))
      2 o2 y6 b* c9 v7 P+ x8 |. b# R
    13. (lldb)
    复制代码
    这个是找不到UIView的frame属性,我们可以制定输出的类型,或者引入UIKit库。
    1. (lldb) expr NSDictionary *$tmpDict = [dataDict[@"Data"] firstObject]
      , K% x& q3 v5 g5 u
    2. error: no known method '-firstObject'; cast the message send to the method's return type
        s7 _$ ~9 S3 S6 H. }: r  o0 {
    3. error: 1 errors parsing expression
      . `9 ^. J/ u1 ^! k! E
    4. (lldb) expr NSDictionary *$tmpDict = (NSDictionary *)[dataDict[@"Data"] firstObject]4 H0 l1 q: @" u
    5. (lldb) po $tmpDict
      : R% w" t( z9 e6 }: D% `+ g9 X
    6. {
      ( ]" \8 o0 W% Y3 K7 I: S( ]
    7.     Key = 1;
      0 h) @) s( R8 t0 K( W( T
    8. }
    复制代码
    这个是找不到-firstObject这个方法。
    总结
    以上这些我提到的方法,只是调试中的冰山一角,lldb里还有很多宝藏去挖掘。在开发项目中,对我们开发有利的去多了解,至于其他更牛逼的技巧(与实际开发项目没有多大关系的),我们有时间、有精力、有想法的可以多去探讨。研究的越多、越广,你会发现自己有很多很多很多很多都不知道,[emoji],发现自己很菜很菜,因为搞技术就像一个无底洞嘛。
    参考
    原文地址:http://joakimliu.github.io/2015/05/22/Some-Debug-Method-in-iOS/
    使用道具 举报 回复
    严禁恶意灌水!!!拒绝伸手党!!!
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    ض