用户
 找回密码
 立即注册

发帖

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]
      " W! Y. U' N* V) {
    2. po [NSString stringWithCString:(char*)[connection.currentRequest.HTTPBody bytes] encoding:4]
      5 k) A% X) ~1 a  b% }
    3. po [NSString stringWithCString:(char*)[data bytes] encoding:4]
    复制代码
    其实上面打印的东西我们都知道,我只是把他们放在了一个断点里面,让可以进行三连击:1、输出请求网址;2、输出请求参数;3、输出返回数据。
    这样不仅解决了NSLog打印出来的中文数据显示为UTF8格式,还能省下我们很多代码量,是不是很nice。。。
    注:AF接收数据的方法 在AFURLConnectionOperation文件
    1. - (void)connection:(NSURLConnection __unused *)connection/ p$ t; Y6 W$ x
    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"
        ?, R# e9 \% c
    2. (lldb) po $xxString+ T. S" e! ~' p3 Z$ R1 o
    3. 111
      % A' C! X5 F; ?1 p

    4. 5 C9 J1 C& {' d' |3 g, V
    5. (lldb) po $xxString = @"2222"
      # z' K1 X9 j& @0 [' f; K8 v. g: U$ r
    6. 2222
      6 ~% ]3 T- B0 H  g2 N  e. X! \

    7. : _1 ?, J# r: z5 D8 l9 [
    8. (lldb) expression $xxString = @"3333"
      : c/ k" E; C, {- \; _- x
    9. (__NSCFString *) $2 = 0x00007fc793675a10 @"3333", h% \+ X0 `( |
    10. (lldb) po $xxString
      ) c( a3 z) \' E& L- P
    11. 3333% |: z0 a* Q. f5 h) R& V! s

    12. . z. B$ D, h4 _8 V
    13. (lldb)
    复制代码
    哦哦,对了 exp Class *$instance是在lldb中创建实例。
    1. (lldb) expression int $b = 10( ]; i8 ]9 a; l* p$ m
    2. (lldb) po $b
      3 B: b0 I' i  w# V' j; f1 A
    3. 10" L6 [/ X7 U" B1 a. O
    4. ! {. a# _( p9 V+ Y5 T2 U8 W2 K
    5. (lldb) exp $b = 100
        v' P2 a& Y6 U3 Q, v
    6. (int) $0 = 1004 ^3 M4 H* B/ ^2 A* E7 ~- v
    7. (lldb) po $b
      1 ~0 p0 o2 m9 x& w9 B4 C6 ]$ I2 |
    8. 100
      & z! F: G' ?3 Q5 S# l- K* X# u
    9. 3 ]. }6 F3 O! {8 [
    10. (lldb)
    复制代码
    当然,我们还可以改变某个对象的属性,比如,如果我想要改变一个视图的背景颜色。
    1. po self.view.backgroundColor = [UIColor redColor]
      . M0 S* B0 m/ J8 ~/ @" K3 U+ |
    2. call imageView.backgroundColor = [UIColor greenColor]
    复制代码
    有时候,我想需要改变某个函数的返回值,来测试函数的稳健性,那可以试试 thread return XXX指令。 看一下下面的代码吧。
    1. /// Value8 n3 L( M. o! U' }. S
    2. NSString *string = [self exeGetReturnString];
      , S4 [2 f7 ]9 w7 P) f' v
    3. & h. k) k6 l# L9 a& d: c. q1 R
    4. /// Method
      , q, I7 t/ h9 Y3 O* y
    5. - (NSString *)exeGetReturnString {/ M9 i5 m+ h7 q

    6. . e/ ]1 [6 f9 b$ C8 d$ ?
    7.     return @"1111";
      , t* E' ?- A  b7 ]
    8. }$ T/ t3 M* h3 s1 U/ \
    9. ( a( e9 [+ [' {, d* o7 Y
    10. /// lldb
      / R% X% e% ?7 L& v+ W6 W" v0 C6 W
    11. (lldb) thread return @"This is Changed String!!!"4 _. a1 a9 S0 H  ~5 H
    12. (lldb) po string
      4 ^+ h  ^- d+ @, F- C7 j
    13. This is Changed String!!!( y' t( ~$ b7 T; e/ Q
    14. ( G% k) h' u& M1 q" _
    15. (lldb)
    复制代码
    我把断点设置在函数exeGetReturnString的返回值前面,可以看出string的值变了。这些改变值的方法,有时在调试程序的时候是非常有用的。
    指定输出的格式
    对于基本简单类型,我们可以指定它的输出类型p/x(x表示输出的类型),这种最能体现在的就是整数类型数据之间二进制、八进制、十进制、十六进制的转换,虽然我们能算出来,但电脑应该算的比我们快,比我们准确吧!好吧,那让我们来一发试试吧。
    1. (lldb) expression int $a = 1
      - j! U$ z; w6 D, \9 ^; @3 T. f
    2. (lldb) po $a
      2 Y8 g$ O2 W% V& v4 H- q. I* o1 l
    3. 1
      * B# e5 ]. `$ b
    4. * T2 ]" i. c0 W& V8 M5 u
    5. (lldb) p/t $a
      0 |' O: c# y8 z! E4 ?  u
    6. (int) $a = 0b00000000000000000000000000000001
      . c8 l# Z) g4 j- {2 H5 S; z; z0 G
    7. (lldb) p/o $a/ a& q( m* p! \$ E8 T) R
    8. (int) $a = 01
      9 z  H6 I: ?' x" x" o% t
    9. (lldb) p/x $a
      3 p% ~+ P. }! x, E: S* X- p4 V
    10. (int) $a = 0x00000001
      7 a  l% k$ o, Q. b( |% T0 o
    11. (lldb) p/d 0b00000000000000000000000000000001
      8 x6 F1 T  \! Q- I0 n% A
    12. (int) $0 = 15 {3 P7 u1 G3 Y2 |1 [
    13. (lldb) p/d 01
      ' _6 H+ E9 |8 H: D
    14. (int) $1 = 1  D: j. g1 E* o  b% @
    15. (lldb) p/d 0x00000001- X% j& g9 A/ D( b
    16. (int) $2 = 1
      : W# f. p- m* u$ m% l- w5 a
    17. (lldb)
    复制代码

    , Q$ a6 d& G# x9 q
    上面用的p/X(X代表进制,t,二进制;o,八进制;d,十进制;x,十六进制)。
    上面输出的$X(0-2),这是啥意思呢?其实可以将他们看作是对操作对象的一个引用,可以直接使用这个符号来操作相对应的对象。
    1. (lldb) expression int $a = 1
        e. L3 H$ o5 J1 }
    2. (lldb) po $a: v2 k, f, ], P6 ]) S( i
    3. 1
      % l0 ~! E! Q+ ~) D

    4. 5 V5 H- _' c( ^
    5. (lldb) p/t $a
      ( ]  a& y7 ~+ v* b/ n
    6. (int) $a = 0b000000000000000000000000000000018 W8 w" y9 ^  W$ H7 j0 d# {1 O0 o
    7. (lldb) p/o $a
      1 `: F0 a( q5 x
    8. (int) $a = 016 e( R5 D) u! E4 z- }; q6 Z3 p
    9. (lldb) p/x $a
      ; n: C3 S# z5 B- o6 {# W
    10. (int) $a = 0x000000017 K, C9 c: W: t: ^
    11. (lldb) p/d 0b000000000000000000000000000000018 d; h& ]0 |0 {% ^- V2 p6 Q3 t3 y
    12. (int) $0 = 1
      ( E* S2 d" S- z% D) a
    13. (lldb) p/d 01
      . [$ b. e, [, e! \- [' z
    14. (int) $1 = 1
      ! G, P1 P+ }( ]7 O+ I9 h8 l# A
    15. (lldb) p/d 0x00000001% b" O' K) N6 `
    16. (int) $2 = 1
      ; ~% T6 m* j9 p2 z
    17. (lldb)
    复制代码
    查找奔溃信息的位置
    程序崩溃了要咋办?找到崩溃的地方,解决它撒。我们可以添加一个全局的断点Add Exception Breakpoint,当程序崩溃的时候,它能定位错误的代码位置。但是还有一种方法能找到错误的位置。image lookup --address 0xXXXXXXXX(0xXXXXXXXX为程序奔溃时控制台给出的地址)。
    1. NSString *originalString = @"0123";8 L' I, i7 d& V" h" j
    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'
      0 ]" t8 K: P' I3 B0 u! k* F
    2. *** First throw call stack:
      : @# S/ q( V5 u# t) s& t# p2 n
    3. (
      . j! d. j, R( D" o
    4.         0   CoreFoundation                      0x0000000104a70c65 __exceptionPreprocess + 165
      ' c( e  t! _+ V# }/ \: m
    5.         1   libobjc.A.dylib                     0x0000000104707bb7 objc_exception_throw + 452 l2 v  ^4 i: Z
    6.         2   CoreFoundation                      0x0000000104a70b9d +[NSException raise:format:] + 205
      . q8 M+ x" f1 N* G% l
    7.         3   Foundation                          0x0000000104276ca8 -[NSString substringFromIndex:] + 118" q/ V- |( ^$ V: L5 e8 n
    8.         4   DebugMethod                         0x00000001041d3936 -[ViewController viewDidLoad] + 710) w( h6 Z$ q8 q5 q) k
    9.         5   UIKit                               0x0000000104f9b210 -[UIViewController loadViewIfRequired] + 738
      + E0 I+ o/ g: d, ?4 O; [
    10.         6   UIKit                               0x0000000104f9b40e -[UIViewController view] + 27
      3 m- t* z% W* ]5 g- S+ O" {3 \
    11.         7   UIKit                               0x0000000104eb62c9 -[UIWindow addRootViewControllerViewIfPossible] + 58
      , w" I7 t' h7 o1 b. g4 _( h+ d6 A$ I
    12.         8   UIKit                               0x0000000104eb668f -[UIWindow _setHidden:forced:] + 247
      0 J9 e: q9 ]1 @* @/ m
    13.         9   UIKit                               0x0000000104ec2e21 -[UIWindow makeKeyAndVisible] + 42+ C5 l; {/ ~. B7 Z6 G
    14.         10  UIKit                               0x0000000104e66457 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2732$ c7 W+ ^& V0 Y% C" s3 ^
    15.         11  UIKit                               0x0000000104e691de -[UIApplication _runWithMainScene:transitionContext:completion:] + 1349
      " }0 K) j4 ~  v' k
    16.         12  UIKit                               0x0000000104e680d5 -[UIApplication workspaceDidEndTransaction:] + 179
      * q' y" O: S1 S& h) a: p
    17.         13  FrontBoardServices                  0x000000010766f5e5 __31-[FBSSerialQueue performAsync:]_block_invoke_2 + 21$ ]. w3 [, F' b( L) ^2 a
    18.         14  CoreFoundation                      0x00000001049a441c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
      & }; m% t5 O' ^3 f
    19.         15  CoreFoundation                      0x000000010499a165 __CFRunLoopDoBlocks + 341
      ( I6 Z; z2 t& n8 F  [% D
    20.         16  CoreFoundation                      0x0000000104999f25 __CFRunLoopRun + 2389
        [* r3 v  W- r+ B$ ^( ?4 I
    21.         17  CoreFoundation                      0x0000000104999366 CFRunLoopRunSpecific + 470
      3 y5 V' B! B1 v$ f
    22.         18  UIKit                               0x0000000104e67b42 -[UIApplication _run] + 413
      . @8 K& f) |8 A" U- I  R" R' E, q
    23.         19  UIKit                               0x0000000104e6a900 UIApplicationMain + 1282" n: ?  L4 E8 l% ^
    24.         20  DebugMethod                         0x00000001041d3d3f main + 111$ r5 z) W* j) o; ]4 W/ b( d! `
    25.         21  libdyld.dylib                       0x000000010703f145 start + 1
      7 R1 x; G; `& T0 S9 K
    26. )
      ; |+ B% P" F4 V' E* \+ c
    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: z2 B, {# H$ d5 {# ?7 N
    2.       Address: DebugMethod[0x0000000100001936] (DebugMethod.__TEXT.__text + 710); ~# Q" _8 J4 ?. s' n. h
    3.       Summary: DebugMethod`-[ViewController viewDidLoad] + 710 at ViewController.m:345 M% y+ g; t: ]
    4. (lldb)
    复制代码
    结果还真的被猜中了,从上面的结果来看,代码错在ViewController.m文件viewDidLoad34行。
    一些问题的处理
    在使用以上一些方法的时候,也遇到了一些问题。有的时候,它找不到对象类型或者方法,比如下面这个:
    1. (lldb) po self.view.frame
      2 M, d5 k4 F: h+ I* V9 y# e
    2. error: property 'frame' not found on object of type 'UIView *'2 g6 k9 d" D5 I  \9 P& t
    3. error: 1 errors parsing expression
        {: g5 T' v% y# q9 Y5 |' S8 w
    4. (lldb) po (CGRect)self.view.frame, m. j# h$ Y& ~& A* c1 h. P( n7 g. K( Y
    5. error: 'CGRect' is not a valid command.* S6 T3 q' _6 y8 l4 n
    6. (lldb) po (CGRect)[self.view frame]; J6 t* ~8 ?1 E" Q: g4 A) B/ U
    7. (origin = (x = 0, y = 0), size = (width = 375, height = 667))
      2 Y# M6 h: L7 `( ?( y
    8. (origin = (x = 0, y = 0), size = (width = 375, height = 667))
      2 n7 P7 Y  k  a
    9. (lldb) exp @import UIKit
      + \# f( f' H2 b* `5 ~' U! A
    10. (lldb) po self.view.frame
      # T' v6 l) B# |! M" Z1 t: }6 c8 V
    11. (origin = (x = 0, y = 0), size = (width = 375, height = 667))+ K, e: N1 G. ~7 d8 Y0 [
    12. (origin = (x = 0, y = 0), size = (width = 375, height = 667)); ~$ ]& `/ Q; o8 w3 T- r: [
    13. (lldb)
    复制代码
    这个是找不到UIView的frame属性,我们可以制定输出的类型,或者引入UIKit库。
    1. (lldb) expr NSDictionary *$tmpDict = [dataDict[@"Data"] firstObject]
      2 Z' d. R0 U) U+ A1 j2 p
    2. error: no known method '-firstObject'; cast the message send to the method's return type3 E5 Z) D- j9 x
    3. error: 1 errors parsing expression- \' F( N+ V& A
    4. (lldb) expr NSDictionary *$tmpDict = (NSDictionary *)[dataDict[@"Data"] firstObject]# h9 W) I- F4 S% W, p* o
    5. (lldb) po $tmpDict
      5 i1 H% w, Z# g' k5 k$ s
    6. {4 j* M2 l0 s4 m3 L  _4 l
    7.     Key = 1;* q1 j( ^. |# h1 L
    8. }
    复制代码
    这个是找不到-firstObject这个方法。
    总结
    以上这些我提到的方法,只是调试中的冰山一角,lldb里还有很多宝藏去挖掘。在开发项目中,对我们开发有利的去多了解,至于其他更牛逼的技巧(与实际开发项目没有多大关系的),我们有时间、有精力、有想法的可以多去探讨。研究的越多、越广,你会发现自己有很多很多很多很多都不知道,[emoji],发现自己很菜很菜,因为搞技术就像一个无底洞嘛。
    参考
    原文地址:http://joakimliu.github.io/2015/05/22/Some-Debug-Method-in-iOS/
    使用道具 举报 回复
    严禁恶意灌水!!!拒绝伸手党!!!
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    ض