iOS内存泄漏的场景和检测

内存泄漏的相关定义

OC当中内存管理方式:ARC/MRC
ARC:自动引用计数(系统自动管理内存),由开发人员开辟内存空间,但是不需要释放该内存空间,系统会自动释放该空间;本质上还是基于MRC的,只不过是系统自动添加了释放内存的方法;
MRC:手动引用计数(手动管理内存),由开发人员开辟内存空间,在使用完对象之后,释放该对象所占用的内存空间;释放时机由开发人员自己释放;

内存泄露:如果程序运行时一直分配内存而不及时释放无用的内存,程序占用的内存越来越大,直到把系统分配给该APP的内存消耗殚尽,程序因无内存可用导致崩溃,这样的情况我们称之为内存泄漏。

内存泄漏的常见场景

1.ARC只负责管理OC对象的内存,CoreGraphics、CoreFoundation等C语言框架下的对象需要手动管理内存。注意以creat,copy,retain作为关键字的函数都是需要释放内存的,注意配对使用。比如:CGColorCreate<-->CGColorRelease

CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceRetain(rgbColorSpace);

解决方法

CGColorSpaceRelease(rgbColorSpace);
CGColorSpaceRelease(rgbColorSpace);
  1. block造成的循环引用
    当前类持有的block中又使用了self或者实例变量都会造成循环引用;单例类持有的block中使用了self或者实例变量
self.myBlock = ^{
        [self testAction];
        //_myTitle = @"MemoryLeak";
    };
[[NSNotificationCenter defaultCenter] addObserverForName:@"" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        [self testAction];
    }];

解决方法(注意在对象持有的block内直接使用实例变量也会循环引用,一定要用弱化过的weakSelf)

__weak typeof(self)weakSelf = self;
self.myBlock = ^{
    [weakSelf testAction];
};
  1. delegate造成的循环引用,一般情况下设置代理属性时使用weak修饰即可

  2. NSTimer & CADisplayLink
    NSTimer会造成循环引用,timer会强引用target即self,在加入runloop的操作中,又引用了timer,所以在timer被invalidate之前,self也就不会被释放。

NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

常见解决方法:
1.适当时机调用 invalidate方法
2.使用YYKit框架下的YYWeakProxy
YYWeakProxy原理:生成一个临时对象,让 timer 强引用这个临时对象,在这个临时对象中弱引用 self

[NSTimer timerWithTimeInterval:5 target:[YYWeakProxy proxyWithTarget:self] selector:@selector(timerAction1:) userInfo:nil repeats:YES];
  1. vcA pushTo vcB, vcA 与 vcB 相互强引用,则vcA和vcB都不能正常释放,弱化其中一方即可打破循环引用,如果此时vcB的某个属性被vcA强引用,则pop后,vcB释放,但该属性不会被正常释放。

  2. 若项目中使用地图相关类,一定要检测内存情况,因为地图是比较耗费App内存的,因此在根据文档实现某地图相关功能的同时,我们需要注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理等滞空为nil,注意地图中标注(大头针)的复用,并且在使用完毕时清空标注数组等。
    这条是我从别的地方看的,自己以前做地图功能时也没在意这个

  3. 大次数循环内存暴涨问题
    该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,解决方法为在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。

for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            NSString *string = @"Abc";
            string = [string lowercaseString];
            NSLog(@"%@", string);
        }
    }

检测内存泄漏的方法

  1. xcode自带的静态分析 Xcode -> Product -> Analyze
    可以检测到如下这些问题:
    (1) 逻辑缺陷,例如访问未初始化的变量;
    (2) 内存管理缺陷,如内存泄露;
    (3) 无用存储缺陷(永不会被访问的变量);
    (4) 因未遵从项目用到的框架(frameworks)或类库(libraries)所规范的而导致的API使用缺陷;

  2. xcdoe自带工具 Instrument -> Leaks
    先看看 Leaks,从苹果的开发者文档里可以看到,一个 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 工具查不出这类内存泄露,应用有限。
这个工具的使用不难,网上很多教程,我这里不再详述,我的感觉是效果不怎么样,没检测出什么有用的

  1. xcdoe自带工具 Instrument -> Allocation
    对于 Abandoned memory,可以用 Instrument 的 Allocations 检测出来。检测方法是用 Mark Generation 的方式,当你每次点击 Mark Generation 时,Allocations 会生成当前 App 的内存快照,而且 Allocations 会记录从上回内存快照到这次内存快照这个时间段内,新分配的内存信息。举一个最简单的例子:
    我们可以不断重复 push 和 pop 同一个 UIViewController,理论上来说,push 之前跟 pop 之后,app 会回到相同的状态。因此,在 push 过程中新分配的内存,在 pop 之后应该被 dealloc 掉,除了前几次 push 可能有预热数据和 cache 数据的情况。如果在数次 push 跟 pop 之后,内存还不断增长,则有内存泄露。因此,我们在每回 push 之前跟 pop 之后,都 Mark Generation 一下,以此观察内存是不是无限制增长。这个方法在 WWDC 的视频里:Session 311 - Advanced Memory Analysis with Instruments,以及苹果的开发者文档:Finding Abandoned Memory 里有介绍。
    用这种方法来发现内存泄露还是很不方便的:
    首先,你得打开 Allocations
    其次,你得一个个场景去重复的操作
    无法及时得知泄露,得专门做一遍上述操作,十分繁琐
    Allocation使用细节见该链接: http://www.cnblogs.com/lxlx1798/p/6933195.html

  2. 第三方检测工具MLeaksFinder
    特点:
    1.使用非常方便。用pod直接导入即可(pod 'MLeaksFinder'),对代码没有任何侵入。按照正常测试流程操作,如果有泄漏,在界面 pop/dismiss 2秒后会有alert弹框提示。
    2.默认自动检测继承自 UIViewController 和 UIView 对象的内存泄露,而且也可以扩展以检测其它类型的对象
    3.能准确显示出哪个对象没有被释放,并可以查看循环引用的关系(依赖Facebook开源的一个循环引用检测工具 FBRetainCycleDetector)
    4.只在 debug 下开启,完全不影响 release 包,当然也可以打包时删除这个库减小包大小

原理:
我们知道,当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view,view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController,它的 view,view 的 subviews 等等是否还存在。
具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是弹出alert弹框。这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会弹框,如果它没被释放(泄露了),-assertNotDealloc 就会被调用。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc,若2秒后没被释放,就会alert。

MLeaksFinder githup地址: https://github.com/Tencent/MLeaksFinder
facebook自动化检测内存泄漏: http://www.cocoachina.com/ios/20170102/18490.html

日常的一些内存使用优化

  1. 优化加载本地图片的缓存
    使用imageNamed加载图片会有缓存,较大的图片和不常使用的本地图片可以用[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"btn_download_n@2x" ofType:@"png"]]加载,注意:使用该方法加载,如果图片名字包含@2x或@3x,一定也要加上,否则无法加载出来

  2. 优化加载网络图片的缓存
    适时地清理缓存。可以在适当的时机调用清除的方法,或者适当时机查询当前的缓存的大小(SDWebImage中提供了接口),超过一定缓存量再清除,这个看自己的需求了

[[SDImageCache sharedImageCache] clearMemory];
[[SDImageCache sharedImageCache] clearDiskOnCompletion:nil];
  1. 优化加载H5页面的缓存
    用UIWebView加载网页,退出控制器内存不减,每次加载持续增涨
    解决方法:
    如果系统版本最低支持iOS8,直接将UIWebView替换成WKWebView即可。
    UIWebView可在加载完成的代理方法中加上清除web缓存的代码
- (void)webViewDidFinishLoad:(UIWebView *)webView {
         //防止内存暴增
         [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"WebKitCacheModelPreferenceKey"];
         [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitDiskImageCacheEnabled"];
         [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitOfflineWebApplicationCacheEnabled"];
         [[NSUserDefaults standardUserDefaults] synchronize];
     }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容