上次介绍了一下LLDB的使用, 但实际上在解决一些问题的时候, 并不是直接使用LLDB调试指令的, 会通过各种工具或技巧来解决问题, 本篇文章就是来探讨一些相关的方法.
1. signal SIGABRT
image.png这在OC中经常出现, 一般情况下, 数组越界
或者调用的方法不存在
, 会触发这个错误
解决方法:
- 方法1. 打开全局断点, 错误一发生, Xcode会自动定位到错误的位置
- 方法2. 运用LLDB的调试指令, 直接寻找引发错误的内存地址
(lldb) image lookup --address 0x00000001087418c3
Address: LCCustomTools_OC[0x000000010000f8c3] (LCCustomTools_OC.__TEXT.__text + 59507)
Summary: LCCustomTools_OC`-[AppDelegate application:didFinishLaunchingWithOptions:] + 195 at AppDelegate.m:29
以上这两种问题, 在Swift中又不一样, 编译器会直接提示代码错误.
数组越界.png 无法调用方法.png
那么这个signal SIGABRT到底是什么呢?
- 这是一个信号,
SIG
是所有unix信号的前缀名,ABRT
是abort program
的简称. - 当操作系统发现不安全的情况时, 如UIKit 等框架通常会在特定的前提条件没有满足或一些糟糕的情况出现时调用 C 函数
abort
(由它来发送此信号)
这里展示了所有的Unix信号
image.png
在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
2. EXC_BAD_ACCESS(Zombie Objects
)
EXC_BAD_ACCESS
,指向某块内存发送消息,但是该内存无法响应对应的消息指令。比如, 向一个已经释放的对象发送消息, 就会报此错误.
在MRC环境下, 测试以下代码,
因为对象arr
已经被释放, 所以再次调用arr
的objectAtIndex:
方法,就会报EXC_BAD_ACCESS
, 也叫坏内存访问, 为了精确定位到到底是哪里的坏内存被访问了, 在Product -> Scheme -> Edict Scheme中, 勾选Zombie Objects
再此运行代码, 就能发现具体出错位置. image.png
- 这里我们能看到arr已经由
NSArray
变成一个僵尸对象
. - 当一个对象的
引用计数
变为0时, 程序会将需要dealloc
的对象转化为僵尸对象
。如果之后再给这个僵尸对象发消息,则抛出异常,并打印出相应的信息
3. Address Sanitizer
但是如果是在 malloc 对象
方面, Zombie Objects
就很难起作用了. 比如下面这种, 明显90已经超出了原来的内存分配范围, 仍然去赋值, 程序会马上退出
还是在原来的界面, 勾选Address Sanitizer
, 再次运行, 就能发现问题所在
报堆内存溢出
出错信息.png
Address Sanitizer 就已经支持的错误检查包括:
- Use-after-free, 野指针问题, 访问已释放的内存区域
- Heap buffer overflow, 堆内存溢出
- Stack buffer overflow, 栈内存溢出
- Global variable overflow, 全局变量溢出
- Overflows in C++ containers
- Use-after-return
- Use-after-scope
4. Memory Leak (内存泄露)
当希望某个对象释放掉的时候,它没有按照预想被释放掉,导致不必要的内存占用, 比较常见的是Retain Circle
(循环引用).
在Teacher.m里
-(void)teach:(void (^)())block
{
self.caseBlock = block;
if (self.caseBlock) {
self.caseBlock();
}
}
在TeacherVC.m里
[self.teacher teach:^ {
self.name = @"jack";
}];
// dealloc
- (void)dealloc
{
NSLog(@"==== dealloc ====");
}
-
当我们
push
到下一个TeacherVC
里时, 执行上面的代码, 再pop
回上一个页面, 会发现本应该执行的dealloc
方法并没有执行, 说明TeacherVC这个控制器没有销毁, 造成了内存泄露. -
原因在于
self
强引用了teacher
,teacher
又强引用了一个block
,而该block
在回调时又调用了self
,会导致该block
又强引用了self
,造成了一个保留环,最终导致self
无法释放。
self -> teacher -> block -> self
解决方法:
__weak typeof(self) weakSelf = self;
[self.teacher teach:^{
typeof(weakSelf) strongSelf = weakSelf;
strongSelf.name = @"jack";
}];
- 通过__weak修饰, 在block回调里面用weakSelf, 打破了闭环
self -> teacher -> block -> strongSelf -> weakSelf
- 一般会在
block
回调里再强引用一下weakSelf
(typeof(weakSelf) strongSelf = weakSelf;),因为__weak
修饰的都是存在栈内,可能会被系统释放,造成后面调用weakSelf
时weakSelf
可能已经是nil
了,后面用weakSelf
调用任何代码都是无效的。
在instruments
里面看到的现象是这样的, 可以很明显看到是在调用teach
方法里面泄露的, 可以在Xcode -> Open Developer Tool -> Instrument, 打开Instrument
网友评论