iOS 究极体测试工具 内存泄漏

一、检测工具介绍

1.1 Instrument — Leaks,Allocations,Analyze

我用到的检测内存泄露的工具主要是Xcode中集成的Leaks组件,这个组件的检测准确率还是比较高的(毕竟水果家亲儿子),可以查看到很多比如说是泄露大小,泄露产生的地方及其堆栈信息等。但是这里的“泄露产生的地方”并不一定可以定位到具体发生泄漏的某一句代码,而是会标出发生泄漏的对象初始化分配内存的地方,然后需要具体去分析该对象来查处泄漏的原因。

参考资料:

1. 【使用Instruments定位iOS应用的Memory Leaks

2. 【Leaks Instrument

Allocations工具是一个跟踪由应用程序分配的对象内存的工具。一般就是用来在疑似内存泄露的地方,通过反复操作,查看某些对象内存是否有被正常的释放,从而得知是否发生内存泄露。(= =。这里我并没使用到这个,这算是以前比较古老的检测内存泄漏的方式了,不过某些情况下也还是挺有用。)

参考资料:

1. 【iOS性能优化:Instruments 工具的救命三招

2. 【IOS性能调优系列:使用Allocation动态分析内存使用情况

Analyze是一款静态分析代码的工具。它可以发现一些逻辑错误,内存泄漏和声明错误(未使用变量)等。这里可以发现的一些内存泄漏问题主要是一些常见的循环引用,CF库对象未release等相对简单的问题,通常是在进行其他方式检测之前就使用的方式,把一些简单的问题先发现并处理了。

参考资料:

1. 【IOS性能调优系列:Analyze静态分析

2. 【IPhone开发工具篇-利用xcode profile和analyze进行性能优化

1.2 内存检测组件

此外还有一些“植入”项目中的内存检测组件,比如说Facebook iOS 内存检测三剑客(FBAllocationTracker/FBMemoryProfiler/FBRetainCycleDetector),MSLeakHunter,MLeaksFinderPLeakSniffer等等。

这些组件的实现原理都是大同小异的。主要就是灵活运用了OC中的Rumtime机制,以及各种OC对象生命周期管理相关的特性。这些组件为了实现对OC对象的内存监控,其本质就是在这些对象被分配和释放的时机进行监测,结合系统对这些对象生命周期管理方法实现是否发生内存泄露检测的目的。

比如说需要监测一个UIViewController类型的对象,就可以联想到iOS中VC的生命周期管理和UINavigationController有很大关系,因为后者在iOS应用者常常被用来管理大量VC的跳转控制。所以就可以考虑通过监控UINavigationController的navigation stack来达到检测VC是否发生内存泄露的目的。(一般以下这些方法都会被hook)

再比如说常见的NSObject对象,其alloc和dealloc方法就是对象生命周期中很重要的两个方法,分别是分配内存资源和释放内存资源时会被调用的方法。然后就可以考虑通过method swizzing方法替换alloc和dealloc这两个方法的实现,这样就可以获得对象内存分配的一些信息。

以下再给出个检测VC内存泄漏的原理:

如何判断VC是否还在内存驻留?

Tips:利用ARC中weak指针指向的对象在对象释放时会自动置为nil的特性来检测VC是否在内存驻留。

在什么时机检测VC是否发生内存泄露?

Tips:通过监控UINavigationController的navigation stack,可以判断一个VC的生命周期的开始和结束。就是当VC从navigation stack移除且VC的viewDidDisappear方法执行时,可以认为一个VC的生命周期即将结束。这时候就可以创建一个指向该VC的weak指针,并初始化一个定时器对VC进行延时扫描,最后通过1中的方法判断VC是否还驻留在内存从而得出VC是否发生内存泄露的结论。

二、应用内存

从苹果的开发者文档里可以看到,一个 app 的内存分三类:

Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).

Abandoned memory: Memory still referenced by your application that has no useful purpose.

Cached memory: Memory still referenced by your application that might be used again for better performance.

其中 Leaked memory 和 Abandoned memory 都属于应该释放而没释放的内存,都是内存泄露,而 Leaks 工具只负责检测 Leaked memory,而不管 Abandoned memory。在 MRC 时代 Leaked memory 很常见,因为很容易忘了调用 release,但在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限。(引用出处【传送门】)

三、实践

3.1 对象内存管理

1. 在MRC模式下,通过new, copy, alloc方式创建的对象,记得release。一般在delloc中进行释放操作。当然局部内产生的也要在局部内进行释放。

点评:呵呵,在实践中发现最多的问题就是这个。尤其是在ARC和MRC都有的项目中。= =。我猜测原因之一可能是后面的代码修改者没意识到当前修改的文件是MRC模式的,所以在新增一些属性或成员变量后,没有在dealloc方法或对象使用完毕后及时的释放资源。

2. 在MRC模式下,发送了retain消息,记得也要发送release消息。并且在一个对象发送retain消息之前,也要考虑是否要release原来的对象。

碰到的一个栗子:

@interface classA{ NSString *_str; } - (void)functionA{ //正确的方式是这里要有: [_str release]; _str = [[NSString stringWithFormat:@"%d", @(213)] retain]; //后续代码... } - (void)functionB{ //正确的方式是这里要有: [_str release]; _str = [[NSString stringWithFormat:@"%d", @(213)] retain]; //后续代码... } @end

  • Tips:这里存在一个问题就是functionA中对一个对象发送了retain消息,如果这时候又调用了functionB方法,str变量被重新赋值。此时如果没有先对str发送release消息的话,则会导致functionA中引用的对象发生内存泄露。

对于一般情况下使用的局部变量都会记得发送retain后发送release,然而在栗子中那种情况下,成员变量可能在不同方法中被重新赋值的时候,就要注意了!

3. 不论是MRC还是ARC情况下,使用Core Foundation框架(C语言实现的框架,其可以和Cocoa Foundation库中的对象进行类型转换)创建的对象需要手动进行内存管理。即需要手动调用CFRetain和CFRelease来管理对象内存。

Tips:这种情况没啥好说的了,就是记得CFRetain、CFRelease和retain、release一样要成对出现~

再多说一点就是Core Foundation框架和Cocoa Foundation对象指针转换的内容。Cocoa Foundation指针与Core Foundation指针转换,需要考虑的是所指向对象所有权的归属。ARC提供了3个修饰符来管理。【参考资料:IOS之Core Foundation框架和Cocoa Foundation框架区别Core Foundation Framework Reference

__bridge,什么也不做,仅仅是转换。此种情况下:

(1). 从Cocoa转换到Core,需要人工CFRetain,否则,Cocoa指针释放后, 传出去的指针则无效。

(2). 从Core转换到Cocoa,需要人工CFRelease,否则,Cocoa指针释放后,对象引用计数仍为1,不会被销毁。

__bridge_retained,转换后自动调用CFRetain,即帮助自动解决上述(1)的情形。

__bridge_transfer,转换后自动调用CFRelease,即帮助自动解决上述(2)的情形。

4. 使用NSAutoreleasePool创建的自动释放池,一定要确保其发送drain或release消息。这样创建的自动释放池对象才会被释放,同时被加入自动释放池的对象才能收到release消息,避免内存泄露。

碰到的栗子:

- (void)functionA{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *str = [[NSString alloc] initWithFormat:@"%d", @(213)]; [str release]; //执行各种代码... if (...){ //执行各种代码... //问题就在这里:return 之前没有释放自动释放池!!! //正确的做法,加上: [pool release]; return; } [pool release]; }

  • Tips:栗子中的案例虽然看起来是个很逗比的错误,不过在实战中已经发现两处了…所以如果是MRC方式下这样使用自动释放池时,记得也要对自动释放池发送drain或release操作。如果是使用ARC的话,则不推荐栗子中使用自动释放池的方式,而是下面这种方式了。

@autoreleasepool { // Code benefitting from a local autorelease pool. }


既然说到自动释放池,那就顺便简单了解一下其实现原理,使用场景和一些注意事项吧。上面也有提到NSAutoreleasePool有两个方法drain和release,关于这两者的区别可以参考这些资料:【NSAutoReleasePool使用中drain和release的区别】【NSAutoreleasePool】。此外,还发现了一篇讲解AutoReleasePool的比较好的文章,里面也有解释了AutoReleasePool释放时间,原理等等:【黑幕背后的Autorelease】。

5. 函数返回的对象,是否加入自动释放池(延迟释放)。从内存管理的规范上来讲,如果一个函数需要返回一个对象,这个对象应该加入自动释放池中(”谁创建,谁释放”)?虽然说从某种角度来说,不加进自动释放池,而是由函数调用者负责该对象的释放也是可行的。如果函数返回的对象没有加入自动释放池,而函数调用者在外部又没有释放该对象,则就有可能造成内存泄露的现象。

(1)OC中有一些对象有多种创建的方法,比如说NSString, NSArray, NSDictionary之类的(还有它们的可变类型)。这些类都提供了两种类型的创建方式,一种是成员函数initWithXXX,另一种则是类函数stringWithXXX, arrayWithXXX(或array), dictionaryWithXXX(或dictionary)这些。

这些方法都是有区别的,第一种方式产生的对象需要手动release来释放内存,第二种方式产生的对象已经被加到autoreleasepool中,不需要手动release来释放内存。所以在项目中也要注意这些对象使用不同创建方式时所采用的不同的对象管理方法,针对这两种对象生成方式,也有很多讨论,大家自己看看吧哈哈哈哈哈。

参考资料:

1.stringWithFormat vs. initWithFormat on NSString

2.objective-C: NSString应该用initWithFormat? 还是 stringWithFormat?

3.Difference between [NSMutableArray array] vs [[NSMutableArray alloc] init]

(2)其中就碰到过Runtime方法中的class_copyIvarList,class_copyMethodList这些方法返回的对象没有被手动释放导致的内存泄漏。因为这些是C实现的函数,是需要手动对函数返回值进行free的,不然则会导致内存泄露。= =。这里也顺便提醒平时需要注意对于C/C++的实现,当见到malloc/new分配的对象,就应该检查该对象有没有对应的free/delete操作,这些地方往往也是内存泄漏产生的地方。

3.2 引用循环

这是无论在MRC还是ARC下都存在的一种导致内存泄露的情况,尤其是在ARC中,如果发生内存泄漏,其一般都会是罪魁祸首。= =。而且个人觉得引用循环这种问题是最难发现和分析的!项目很大的时候,模块间会很复杂,相互间依赖就很多,一不小心就很容易产生强引用循环这种现象。尤其是在使用到block的时候,更要注意适当处理以避免强引用循环的发生。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容