lldb(gdb)常用的调试命令?
- breakpoint 设置断点定位到某一个函数
- n 断点指针下一步
- po打印对象
在初始化变量的时候,打一个断点,来初始化这个观察点
当程序运行到这个断点时,我们通过 lldb 命令watchpoint set v string_weak_
设置观察点,其中string_weak_
是变量的名字。观察点设置成功之后,可以看到相关的日志提示。- (void)instrumentObjcMessageSends(YES);
更多 lldb(gdb) 调试命令可查看
- The LLDB Debugger ;
- 苹果官方文档: iOS Debugging Magic 。
为了展示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下_objc_msgForward
是如何进行转发的。
首先开启调试模式、打印出所有运行时发送的消息: 可以在代码里执行下面的方法:
(void)instrumentObjcMessageSends(YES);或者断点暂停程序运行,并在 gdb 中输入下面的命令:
call (void)instrumentObjcMessageSends(YES)以第二种为例,操作如下所示:
之后,运行时发送的所有消息都会打印到/tmp/msgSend-xxxx
文件里了。
终端中输入命令前往:
open /private/tmp
可能看到有多条,找到最新生成的,双击打开
在模拟器上执行执行以下语句(这一套调试方案仅适用于模拟器,真机不可用,关于该调试方案的拓展链接: 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]));
}
}
你可以在/tmp/msgSend-xxxx
(我这一次是/tmp/msgSend-9805
)文件里,看到打印出来:
- 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
消息转发做的几件事:
- 调用
resolveInstanceMethod:
方法 (或resolveClassMethod:
)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend
流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod
。如果仍没实现,继续下面的动作。 - 调用
forwardingTargetForSelector:
方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。 - 调用
methodSignatureForSelector:
方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector
抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:
。 - 调用
forwardInvocation:
方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。 - 调用
doesNotRecognizeSelector:
,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。
上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
也就是说_objc_msgForward
在进行消息转发的过程中会涉及以下这几个方法:
resolveInstanceMethod:
方法 (或resolveClassMethod:
)。forwardingTargetForSelector:
方法methodSignatureForSelector:
方法forwardInvocation:
方法doesNotRecognizeSelector:
方法
为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“ _objc_msgForward_demo
”,可运行起来看看。
下面回答下第二个问题“直接_objc_msgForward
调用它将会发生什么?”
直接调用_objc_msgForward
是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。
如何调试BAD_ACCESS错误
- 重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object
- 通过 Zombie
- 设置全局断点快速定位问题代码所在行
- Xcode 7 已经集成了BAD_ACCESS捕获功能:Address Sanitizer。 用法如下:在配置中勾选✅Enable Address Sanitizer