ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【iOS】内存走漏检查及原因分析 [打印本页]

作者: 刘俊凯    时间: 2024-8-4 12:02
标题: 【iOS】内存走漏检查及原因分析
前言
   
  
为什么要检测内存走漏?

敏捷膨胀的内存可以很快让步伐毙命,以是要多加防范。即使有 ARC(自动引用计数)内存管理机制,但在现实中对象之间引用复杂,循环引用导致的内存走漏仍然难以制止,以是关键时候还要独立更生。分析内存泄露不能把全部的内存泄露查出来,有的内存泄露是在运行时,用户操作时才产生的。
什么是内存走漏?

内存走漏指的是一块内存被分配后不再利用,但是没有被步伐正确开释回系统,从而导致该内存继承占用在步伐中,无法被其它任务利用。
这通常发生在利用了动态内存分配但未及时或正确开释,或者由于编程逻辑错误导致。假如不加以管理,会导致步伐斲丧过多的内存,甚至导致应用步伐崩溃。
比如下MRC中如下代码会造成走漏:
  1. NSString* string = [[NSString alloc] init];
  2. ...
  3. // [string release];  //ARC下,编译器自动添加此代码
复制代码
但由于ARC机制,编译器会在得当的时机帮我们加上release代码,制止了内存走漏。不外即使在ARC中也有肯能因对象不开释而引起内存走漏,比如利用CF框架下的对象而没有做CFRelease操作。
固然string所占的内存很小可以忽略不计,但也是有安全隐患的,就像前言所述,代码中这里走漏一点内存,那里又走漏一点内存,反反复复,内存总会有用尽的那一刻。
究竟系统自己内存有限,分配给每个App的内存更加有限,当系统内存逐步不足时,我们的App会变得越来越卡顿。
当系统内存告急时,App中首先会收到didRecieveWarning提醒,假如我们不第一时间采取步伐开释内存,那么系统就会把我们的App Kill掉,以是我们应该重视内存走漏问题。
didRecieveWarning调用流程看这篇文章:【iOS】didReceiveMemoryWarning实例方法
内存走漏排查方法

1. 利用Zombie Objects

偶然间我们会收到EXC_BASD_ACCESS错误提示,但没能跳到详细的堕落代码行,此时可以启用Zombie Objects功能,来探求那些已被开释的对象。
进入edit Scheme:

选中僵尸对象选项:

按照以上步骤开启Zombies Objects,而后Memory查看器变为disable:

系统在即将回收对象时,假如发现通过环境变量启用了僵尸对象功能,那么还将执行一个附加步骤,即把对象转化为僵尸对象,而不彻底回收。
测试代码:
  1. void PrintClassInfo(id obj) {
  2.     Class cls = object_getClass(obj);
  3.     Class superCls = class_getSuperclass(cls);
  4.     NSLog(@"=== %s : %s ===", class_getName(cls), class_getName(superCls));
  5. }
  6. - (void)viewDidLoad {
  7.     [super viewDidLoad];
  8.     // Do any additional setup after loading the view.
  9.    
  10.     UIView* view = [[UIView alloc] init];
  11.     NSLog(@"Before release:");
  12.     PrintClassInfo(view);
  13.    
  14.     [view release];
  15.    
  16.     NSLog(@"After release:");
  17.     PrintClassInfo(view);
  18. }
复制代码

2. 静态分析

打开Xcode项目,并点击Product->Analyze:

静态内存走漏分析如下:


静态分析方法能发现大部分的问题,但是只能是静态分析结果,有一些并不准确,另有一些动态分配内存的情形并没有举行分析。以是仅仅利用静态内存走漏分析得到的结果并不是非常可靠,假如需要,我们需要将对项目举行更为美满的内存走漏分析和排查。那就需要用到我们下面要介绍的动态内存走漏分析方法Instruments中的Leaks方法举行排查。
3. 动态分析方法

打开Xcode项目,点击Product->rofile:

选择Leaks,这时项目也在模仿器或真机上运行起来了:

或者直接在自己的项目中运行步伐,选中Memory点击右上角的Profile in instruments:

都可以进入下面的页面:

由于 Leaks 是动态监测,以是我们需要手动操作 APP,举行测试,一边操作 APP,一边观察 Leaks 的变化,在 暂停按钮 的右边 我们可以选择正在 运行的步伐 & 选择设备 & App, 之后点击 红点 Record(红色圆圈按钮)运行。

观察,假如发如今 Leaks 里面有一个 红色X,这阐明白我们的 APP 存在内存泄露。
就像如许

点击暂停,点击此中一个,然后我们开始分析。
定位修改

此时选中有红色叉的 Leaks,下面有个Leaks 字方格,点开,选中 Call Tree。

接着就是最关键的一步,在这个界面的右下角有多少选框,选中Invert Call Tree(从上到下跟踪堆栈信息) 和 Hide System Libraries(表现隐蔽系统的函数)

在详情面板选中体现的多少条中的一条,双击,会自动跳到内存泄露代码处,然后点击右上角 Xcode 图标举行修改。
Leaks界面分析

Leaks 启动后会开始录制,随着对模仿器运行的 App 的操作,可以在 Leaks 中查看内存占用的情况。
Leaks顶部分为两栏:Allocations 和 Leaks,右侧的曲线代表内存分配和内存走漏曲线。
Call Tree的四个选项:


内存走漏原因分析

在目前主要以ARC举行内存管理的开发模式,导致内存走漏的根本原因是代码总存在循环引用,从而导致一些内存无法开释,这就会导致dealloc方法无法被调用。
开启了ARC并不是就不会存在内存问题,苹果有句名言:ARC is only for NSObject。
利用ARC的项目,一般内存走漏都是 malloc、自定义结构、资源引起的,多留意这些地方举行分析。
注:假如你的项目利用了ARC,随着你的操作,不停开启或关闭视图,内存可能持续上升,但这不肯定表现存在内存走漏,ARC开释的时机是不固定的。
引起内存走漏的几种原因:
1. Leaked Memory:应用步伐未引用的、不能再次利用或开释的内存。


2. Abandoned Memory: 内存仍被应用步伐所引用,没有任何有用的用途。

大多是OC对象、block、timer、delegate等循环引用问题,造成引用计数不停不为零。

3. Cached Memory:内存仍然由应用步伐引用,可以再次利用以得到更好的性能。

为了快速访问而存储起来的对象。
以缓存图片提高性能为例:
  1. @class UIImage;
  2. @interface ImageCache : NSObject
  3. @property (nonatomic, strong) NSMutableDictionary* cache;
  4. + (instancetype)sharedInstance;
  5. - (UIImage *)imageForKey:(NSString *)key;
  6. - (void)setImage:(UIImage *)image forKey:(NSString *)key;
  7. @end
  8. //  ImageCache.m
  9. #import "ImageCache.h"
  10. #import "UIKit/UIImage.h"
  11. @implementation ImageCache
  12. + (nonnull instancetype)sharedInstance {
  13.     static ImageCache *sharedInstance = nil;
  14.     static dispatch_once_t onceToken;
  15.     dispatch_once(&onceToken, ^{
  16.         sharedInstance = [[self alloc] init];
  17.         sharedInstance.cache = [NSMutableDictionary dictionary];
  18.     });
  19.     return sharedInstance;
  20. }
  21. - (void)setImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key {
  22.     self.cache[key] = image;
  23. }
  24. - (nonnull UIImage *)imageForKey:(nonnull NSString *)key {
  25.     return self.cache[key];
  26. }
  27. @end
  28. // 使用缓存
  29. UIImage* image = [UIImage imageNamed: @"example.png"];
  30. [[ImageCache sharedInstance] setImage: image forKey: @"example"];
  31. // 之后访问缓存的图片
  32. UIImage* cachedImage = [[ImageCache sharedInstance] imageForKey: @"example"];
  33. ```
  34. 示例中,图片被缓存以便快速访问,从而提高性能。缓存图片使用的内存不是泄漏,因为这戏内存是有意保留以供将来使用的。
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4