iOS实录15:浅谈iOS Crash

1,593 阅读17分钟

导语:在当前的iOS开发中,虽然ARC为开发者解决了手动内存管理时代 的许多麻烦,但是内存方面的问题依然是产生iOS Crash的元凶之一,本文介绍内存方面,有关僵尸对象、野指针、内存泄漏、废弃内存这四类问题的调试方法和代码中的注意事项。

一、僵尸对象(Zombie Objects) 1、概述

僵尸对象:已经被释放掉的对象。一般来说,访问已经释放的对象或向它发消息会引起错误。因为指针指向的内存块认为你无权访问或它无法执行该消息,这时候内核会抛出一个异常( EXC ),表明你不能访问该存储区域(BAD ACCESS)。(EXC_BAD_ACCESS类型错误)

调试解决该类问题一般采用NSZombieEnabled(开启僵尸模式)。

2、使用NSZombieEnabled

Xcode提供的NSZombieEnabled,通过生成僵尸对象来替换dealloc的实现,当对象引用计数为0的时候,将需要dealloc的对象转化为僵尸对象。如果之后再给这个僵尸对象发消息,则抛出异常。先选中Product -> Scheme -> Edit Scheme -> Diagnostics -> 勾选Zombie Objects 项,显示如下:

设置NSZombieEnabled.png 然后在Product -> Scheme -> Edit Scheme -> Arguments设置NSZombieEnabled、MallocStackLoggingNoCompact两个变量,且值均为YES。显示如下:

设置NSZombieEnabled和MallocStackLoggingNoCompact.png 仅设置Zombie Objects的话,如果Crash发生在当前调用栈,系统可以把崩溃原因定位到具体代码中;但是如果Crash不是发生在当前调用栈,系统仅仅告知崩溃地址,所以我们需要添加变量MallocStackLoggingNoCompact,让Xcode记录每个地址alloc的历史,然后通过命令将地址还原出来。

Xcode 6之前还可以使用gdb,可以使用info malloc-history address命令来将发生崩溃的地址还原成具体的代码行,Xcode 7之后只能使用lldb,使用命令bt来打印调用堆栈。下面是某Crash通过僵尸模式调试,使用bt查看的效果。

bt效果.png 说明:发版前要将僵尸对象检测这些设置都去掉,否则每次通过指针访问对象时,都去检查指针指向的对象是否为僵尸对象,这就影响效率了。

3、代码中的注意事项

在ARC时代,避免访问释放掉的内存,代码需要注意的地方有:

检查代码1 :不能使用assgin或 unsafe_unretained修饰指向OC对象的指针

assgin和unsafe_unretained表示不持对象,是弱引用。如果指针指向的对象被释放了,它们就变成了野指针,很有可能发生Crash。

建议1: assign仅用于修饰NSInteger等OC基础类型,以及short、int、double、结构体等C数据类型,不修饰对象指针;

建议2: OC对象属性一般使用strong关键字(默认)修饰。

建议3: 如果需要弱引用OC对象,建议使用weak关键字,因为被weak指针所引用的对象被回收后,weak指针会被赋为nil(空指针),给nil发任何消息都不会出问题。使用weak修饰代理对象属性就是很好的例子。

检查代码2 :Core Foundation等底层操作

Core Foundation等底层操作它们不支持ARC,还需要手动内存管理。

建议: 注意CF对象的创建和释放。

二、野指针(Wild pointer) 1、概述

野指针是指向一个已删除的对象 或 未申请访问受限内存区域的指针。而这里的野指针主要是对象释放后,指针未置空导致的野指针。该类Crash发生比较随机,找出来比较费劲,比较常见的做法是,在开发阶段,提高这类Crash的复现率,尽可能得将其发现并解决。

向OC对象发出release消息,只是标记对象占用的那块内存可以被释放,系统并没有立即收回内存;如果此时还向该对象发送其他消息,可能会发生Crash,也可能没有问题。下图是 访问野指针(指向已删除对象的指针)可能发生的情况。

访问野指针可能发生的情况图.png 从上图可以知道,野指针造成的Crash的随机性比较大,但是被随机填入的数据是不可访问的情况下,Crash是必现的。我们的思路是:想办法给 野指针指向的内存填写不可访问的数据,让随机的Crash变成必现的Crash。 2、设置Malloc Scribble

Xcode提供的Malloc Scribble,可以将对象释放后在内存上填上不可访问的数据,将随机发生变成不随机发生的事情,选中Product->Scheme->Edit Scheme ->Diagnostics - >勾选 Malloc Scribble项,结果如下:

设置Malloc Scribble.png 设置了Enable Scribble,在对象申请内存后在申请的内存上填0xaa,内存释放后在释放的内存上填0x55;如果内存未被初始化就被访问,或者释放后被访问,Crash必现。

黄曲霉素,环氧化苯比,通用试剂,药物合成试剂,……。 免费溶解。高纯99.99% 帮您解忧。联系q 2081033207

说明:该方法必须连接Xcode运行代码才发现,不适合测试人员使用。可以基于fishhook ,选择hook对象释放的接口(C的free函数),达到和设置Enable Scribble一样的效果。详情参考如何定位Obj-C野指针随机Crash(一):先提高野指针Crash率、如何定位Obj-C野指针随机Crash(二):让非必现Crash变成必现 和如何定位Obj-C野指针随机Crash(三):加点黑科技让Crash自报家门

3、代码中的注意事项

检查使用assgin或 unsafe_unretained 修饰指向OC对象的指针 和 Core Foundation等底层操作。

三、内存泄漏(Memory Leak) 1、概述

内存泄漏是指没有释放掉不再引用对象的内存。即便ARC帮我们解决很多麻烦,但是内存泄漏问题依然比较多;一般开发结束后,都要做一些基本的内存泄漏排查工作。

内存泄漏排查,一般采用Analyzer(静态分析) + Leaks + MLeaksFinder (第三方工具)

2-1、排查之静态分析(Analyzer)

Xcode提供的 Analyzer可以在程序没运行的时候,通过分析代码上下文的语法结构和内存情况,找出代码中潜在错误,如内存泄露、未使用函数和变量等。选中Product->Analyze(快捷键command+shift+B)可以使用了。

Analyzer主要分析四种问题:

逻辑错误:访问空指针或未初始化的变量等; 内存管理错误:如内存泄漏等;Core Foundation不支持ARC 声明错误:从未使用过的变量; API调用错误:未包含使用的库和框架。 **Analyzer执行后,常见的警告类型有: **

1)内存警告(Memory)

eg:

  • (UIImage *)clipImageWithRect:(CGRect)rect{

    CGFloat scale = self.scale; CGImageRef clipImageRef = CGImageCreateWithImageInRect(self.CGImage, CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale));

    CGRect smallBounds = CGRectMake(0, 0, CGImageGetWidth(clipImageRef)/scale, CGImageGetHeight(clipImageRef)/scale); UIGraphicsBeginImageContextWithOptions(smallBounds.size, YES, scale); CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM(context, 0, smallBounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); CGContextDrawImage(context, CGRectMake(0, 0, smallBounds.size.width, smallBounds.size.height), clipImageRef);

    UIImage* clipImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext(); CGImageRelease(clipImageRef); //不添加,内存泄漏,会警告:Potential leak of an object stored into 'clipImageRef' return clipImage; } 分析:Analyzer检查出来内存泄漏,比较常见的就是CG、CF开头的内存泄漏,内存申请,忘记释放了。还有一种是,C申请的内存,没有配对使用new delete, malloc free。

2)无效数据警告(Dead store)

eg:

//错误做法,Analyzer分析后会告知:Value stored to ‘dataArray’ during its initialization is never read NSMutableArray *dataArray = [[NSMutableArray alloc] init]; dataArray = _otherDataArray;

//正确做法 NSMutableArray *dataArray = nil; dataArray = _otherDataArray; 分析: dataArray已经被初始化分配了内存,然后被另一个可变数组赋值,导致一个数据源却申请了两块内存,造成了内存泄露。

3)逻辑错误监测(Logic error)

eg:

//错误做法,Analyzer分析后会告知:Property of mutable type ’NSMutableArray’ has ‘copy’ attribute,an immutable object will be stored instead @property (nonatomic, copy) NSMutableArray *dataArr;

//黄曲霉素,环氧化苯比,通用试剂,药物合成试剂,……。 //免费溶解。高纯99.99% //帮您解忧。联系q 2081033207

//正确做法 @property (nonatomic, strong) NSMutableArray *dataArr;
分析: NSMutableArray是可变数据类型,应该用strong来修饰其对象。

说明: Analyzer由于是编译器根据代码进行的判断, 做出的判断不一定会准确, 因此如果遇到提示, 应该去结合代码上文检查一下;还有某些造成内存泄漏的循环引用通过Analyzer分析不出来。

2-2、排查之内存泄漏工具(Leaks)

Xcode提供的Leak可以帮助发现运行着的程序内存泄漏的地方。通过选中Product-> Profile(快捷键command+i,唤起Instrument工具界面) -> Leaks。切换到Call Tree模式,底部选中Separate by Thread(按线程分开做分析)、Invert Call Tree(反向输出调用树)、Hide System Libraries(隐藏系统库文件)。最后点击红色按钮开始“录制”,效果如下图:

Leaks调试界面.png 在Leaks调试界面上,1是Allocations 模板,显示内存分配情况;2是 Leaks 模板,这里可以查看内存泄露情况。如果红X出现, 表示有内存泄露;主框体区域则会显示泄露的对象。Call Tree选项介绍如下: Call Tree 中选项 说明 Separate by Category 按类型分类,展开All Heap Allocations这一套显示的就是不同方法里堆内存的分配情况 Separate by Thread 按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程,它要处理和渲染所有的接口数据,一旦受到阻塞,程序必然卡顿或停止响应。 Invert Call Tree 反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。 Hide System Libraries 隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。 Flattern Recursion 拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条 2-3、排查之MLeaksFinder(强烈推荐)

MLeaksFinder是微信阅读团队为了简化内存泄漏排查工作,推出的第三方工具,也是我们当前项目中内存泄漏的工具之一。

特点:集成简单,主要检查UI方面(UIView 和 UIViewController)的泄漏。

原理:不入侵开发代码,通过hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法,检查ViewController对象被 pop 或 dismiss 一小段时间后,看看该ViewController对象的 view,view 的 subviews 等等是否还存在。

实现:为基类 NSObject 添加一个方法 -willDealloc 方法,利用weak指针指向自己,并在一小段时间(3秒)后,再次检测该weak指针是否有效,有效则内存泄漏。

集成:通过Cocoapods引入或直接把代码拖进项目,很方便。发生内存泄漏,会弹出警告框,提示发生内存泄漏的位置。

说明:详细内容请参考:MLeaksFinder:精准 iOS 内存泄露检测工具和MLeaksFinder 新特性

3、代码中的注意事项(ARC下的循环引用是内存泄漏的主要原因)

检查代码1 :Core Foundation、Core Graphics等操作

Core Foundation、CoreGraphics等操作不支持ARC,还需要手动内存管理。

建议: 注意CF、CG对象的创建和释放。

检查代码2 :NSTimer/CADisplayLink的使用,因为NSTimer/CADisplayLink对象的target会强引用self,而self又强引用NSTimer/CADisplayLink对象。

建议:使用扩展方法,使用block或 target弱引用目标对象 打破保留环,具体实现参考iOS实录8:解决NSTimer/CADisplayLink的循环引用

检查代码3 :block使用代码。

建议:成对使用weakSelf和strongSelf来打破block循环引用(对于self没有引用的block是不会造成循环引用,不需要使用weakSelf和strongSelf)

原理:在block外定义弱引用(weakSelf),指向的self对象;在block内捕获的是这个弱引用(weakSelf),保证了self不会被block所持有;在执行block内方法时,生成强引用(strongSelf),指向了弱引用(weakSelf)所指向的对象(self对象);在block内部实际是持有了self对象,但是这个强引用(strongSelf) 的生命周期只在这个block执行的过程中,block执行执行完立刻就被释放了。

四、废弃内存(Abandoned Memory) 1、概述#####

废弃内存(Abandoned Memory)指,依然被引用对象的内存,但在程序逻辑中无法再被利用。

排查该类问题建议使用Xcode提供的Allocation,Allocation可以跟踪应用的内存分配情况。

//致死 //黄曲霉素,环氧化苯比,通用试剂,药物合成试剂,……。 //免费溶解。高纯99.99% //帮您解忧。联系q 2081033207

2、使用Allocation

Xcode提供的Allocation由于可以跟踪应用的内存分配情况。开发者反复操作App,查看内存基线变化;甚至还可以设置Mark Generation来对比多次Generation之间的内存增长,这部分的增长就是我们没有及时释放的内存。通过Product-> Profile(快捷键command+i,唤起Instrument工具界面) -> Allocations。最后点击红色按钮开始“录制”,效果如下图:

Allocation界面Statistics Detail 下显示.png 上图是Statistics Detail Type下的界面展示,下面是一些名称的说明: Detail列名 说明 Graph 类型的选择项 Category 类型,或CF对象,或OC对象,或原始块的内存 Persistent Bytes 未释放的内存和大小 Persistent 未释放的对象个数 Transient 已经释放的对象个数 Total Bytes 总使用内存大小 Total 总使用对象个数 Transient / Total Bytes 已释放内存大小/总使用内存大小 Allocation Type 说明 All Heap & Anonymous 所有堆内存和其他内存 All Heap Allocations 所有堆内存 All Anonymous VM 所有其他内存 下图是切换到Call Tree下的界面展示。

Allocation界面Call Tree下显示.png Call Tree列名 说明 Bytes Used 已经使用的内存大小 Count 符号使用的总个数 Symbol Name 符号名称 说明:这些名词的具体解释见Instrument-Allocations

间隔一段时间(如2分钟)点击“Mark Generation”,判断几次之间Generation之间的内存增长,而这些增长可能就是未能及时释放的内存:根据内存占用的比例,找到占用比例最高的那部分,然后找到我们自己的代码,再来分析并解决问题。

Allocation界面Mark Generation下显示.png 3、代码中的注意事项

黄曲霉素,环氧化苯比,通用试剂,药物合成试剂,……。 免费溶解。高纯99.99% 帮您解忧。联系q 2081033207 致死

略,与内存泄漏部分代码中的注意事项相同。

End#### 参考文章:

Xcode执行Analyze静态分析 使用Instruments Allocations排查内存释放不及时的问题 关于Instruments-Leaks工具的归纳总结 MLeaksFinder:精准 iOS 内存泄露检测工具 MLeaksFinder 新特性

相关文章 iOS实录14:浅谈iOS Crash(一)

我是南华coder,一名北漂的初级iOS程序猿。iOS实(践)录系列是我的一点开发心得,希望能够抛砖引玉。

小礼物走一走,来简书关注我

赞赏支持 iOS实践录 © 著作权归作者所有 举报文章 96 关注南华coder 写了 61403 字,被 1030 人关注,获得了 951 个喜欢 iOS 程序猿;江湖再见 喜欢 59 更多分享 Web note ad 1 被以下专题收入,发现更多相似内容 iOS内存泄露 程序员 iOS && ... 待阅读的文章 首页投稿(暂停... 移动前沿 理论 展开更多 240 浅谈iOS Crash(二) 一、僵尸对象(Zombie Objects) 1、概述 僵尸对象:已经被释放掉的对象。一般来说,访问已经释放的对象或向它发消息会引起错误。因为指针指向的内存块认为你无权访问或它无法执行该消息,这时候内核会抛出一个异常( EXC ),表明你不能访问该存储区域(BAD ACCE...

48 CatherinePlans 240 网易iOS App运行时Crash自动防护实践 Baymax:网易iOS App运行时Crash自动防护实践 版权声明本文转自网易杭州前端技术部公众号,由作者授权发布。 前言 大白(Baymax),迪士尼动画《超能陆战队》中的健康机器人,是一个体型胖胖的充气机器人,因呆萌的外表和善良的本质获得大家的喜爱,被称为“萌神”。...

48 IOS开发攻城狮_Fyc 240 iOS崩溃crash大解析 前言 iOS崩溃是让iOS开发人员比较头痛的事情,app崩溃了,说明代码写的有问题,这时如何快速定位到崩溃的地方很重要。调试阶段是比较容易找到出问题的地方的,但是已经上线的app并分析崩溃报告就比较麻烦了。 之前我总是找到一个改一个,并靠别人测试重现来找出问题的地方,这样往...

48 齐滇大圣 Android - 收藏集 Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity的显示之ViewRootImpl初探 Activity的显示之Window和View Android系统的创世之初以及Activity的生命周期 图解Andro...

48 passiontim 回忆 回想过去的一年,找工作真的是很艰难的事,运气也是实力的一部分,一直这样安慰自己,但到了真正的时候又会觉得自己怎么怎样的不幸运,北京的短短旅程给了自己很大勇气,真的挺好。在关上门的时候总还有一扇窗。找到工作,尽管不满意但也干了。每天浑浑噩噩,不知道干啥,干的别人老不满意,却不...

48 记然 海岛,我等你的地方 我出生的那年,有一位年轻的诗人殒灭了,他们说,从此再也没有诗。 什么是诗?可能每个中国人最早记诵的诗莫过于“鹅鹅鹅,曲项向天歌”、“床前明月光、疑是地上霜”……白描一幅小景,寄托某种情感抑或描摹美好事物。再后来我们记诵“千山鸟飞绝”、“夜雨剪春韭”、“自在花飞轻似梦”……小...

48 冷潇湘 诉讼费计算器 祝你打赢官司~ 现代社会法律观念得到极大普及,人民群众解决问题由依靠暴力转而求助法律。企业为了最大限度地保护自身权益,遇到大问题第一时间也会寻求法律的保护,但是职场上法律小白太多,碰到法律问题常常不明所以,手足无措。如果有朝一日你的老板正为处理诉讼费用一筹莫展,而你登录诉讼费计算器网站,5...

48 361d6816c41e 八字泄天机:男优之死 想要感慨的是,有些东西真的是命中注定,比如先天禀赋。为什么此人会是一名男优,走上这样的人生道路,完全可以从其八字看出,又为什么会在这年突然死去。他与妻子的关系是否也有因缘? 此例是一Adult Film Star 的八字, 乙丑年,丁亥月,壬子日。 日主为壬水,亥子丑三合水...

48 Mac李得宝 品牌广告主下手DSP难,谁之过? 过去的2013年里,数字广告圈很热闹,以RTB、程序化购买为代表的新营销模式正引爆着国内外数字营销行业的巨大变革,其高效、实时、精准的优势正在给营销者带来一片新的蓝海。随着DSP企业的大量出现,BAT等巨头相继进入到程序化交易产业链中,程序化购买领域呈现出蓬勃发展的姿态。但...

48 智子云