lldb(gdb)常用的调试命令?

  • breakpoint 设置断点定位到某一个函数
  • n 断点指针下一步
  • po打印对象
  • 在初始化变量的时候,打一个断点,来初始化这个观察点
    当程序运行到这个断点时,我们通过 lldb 命令 watchpoint set v string_weak_设置观察点,其中 string_weak_ 是变量的名字。观察点设置成功之后,可以看到相关的日志提示。
  • (void)instrumentObjcMessageSends(YES);

更多 lldb(gdb) 调试命令可查看

  1. The LLDB Debugger
  2. 苹果官方文档: iOS Debugging Magic

为了展示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下_objc_msgForward是如何进行转发的。
首先开启调试模式、打印出所有运行时发送的消息: 可以在代码里执行下面的方法: (void)instrumentObjcMessageSends(YES);或者断点暂停程序运行,并在 gdb 中输入下面的命令: call (void)instrumentObjcMessageSends(YES)以第二种为例,操作如下所示:
调试技巧 - 图1
之后,运行时发送的所有消息都会打印到/tmp/msgSend-xxxx文件里了。
终端中输入命令前往: open /private/tmp调试技巧 - 图2
可能看到有多条,找到最新生成的,双击打开
在模拟器上执行执行以下语句(这一套调试方案仅适用于模拟器,真机不可用,关于该调试方案的拓展链接: Can the messages sent to an object in Objective-C be monitored or printed out? ),向一个对象发送一条错误的消息: // // main.m // CYLObjcMsgForwardTest // // Created by http://weibo.com/luohanchenyilong/. // Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved. //

import

import “AppDelegate.h”

import “CYLTest.h”

int main(int argc, char argv[]) { @autoreleasepool { CYLTest test = [[CYLTest alloc] init]; [test performSelector:(@selector(iOS程序犭袁))]; return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }调试技巧 - 图3
你可以在/tmp/msgSend-xxxx(我这一次是/tmp/msgSend-9805)文件里,看到打印出来:
调试技巧 - 图4

  • CYLTest NSObject initialize
  • CYLTest NSObject alloc
  • CYLTest NSObject init
  • CYLTest NSObject performSelector:
  • CYLTest NSObject resolveInstanceMethod:
  • CYLTest NSObject resolveInstanceMethod:
  • CYLTest NSObject forwardingTargetForSelector:
  • CYLTest NSObject forwardingTargetForSelector:
  • CYLTest NSObject methodSignatureForSelector:
  • CYLTest NSObject methodSignatureForSelector:
  • CYLTest NSObject class
  • CYLTest NSObject doesNotRecognizeSelector:
  • CYLTest NSObject doesNotRecognizeSelector:
  • CYLTest NSObject class结合《NSObject官方文档》,排除掉 NSObject 做的事,剩下的就是_objc_msgForward消息转发做的几件事:
  1. 调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。
  2. 调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
  3. 调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:
  4. 调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
  5. 调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
也就是说_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:

  1. resolveInstanceMethod:方法 (或 resolveClassMethod:)。
  2. forwardingTargetForSelector:方法
  3. methodSignatureForSelector:方法
  4. forwardInvocation:方法
  5. doesNotRecognizeSelector: 方法

为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“ _objc_msgForward_demo ”,可运行起来看看。
下面回答下第二个问题“直接_objc_msgForward调用它将会发生什么?”
直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。

如何调试BAD_ACCESS错误

  1. 重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object
  2. 通过 Zombie 调试技巧 - 图5
  3. 设置全局断点快速定位问题代码所在行
  4. Xcode 7 已经集成了BAD_ACCESS捕获功能:Address Sanitizer。 用法如下:在配置中勾选✅Enable Address Sanitizer