iOS 内存泄漏排查方法及原因分析
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、排查方法</li><ul class="second_class_ul"><li>1.1 静态内存泄漏分析方法:</li><li>1.2 动态内存泄漏分析方法:</li></ul><li>二、内存泄漏的原因分析</li><ul class="second_class_ul"><li>2.1 ViewController中存在NSTimer</li><li>2.2 ViewController中的代理delegate</li><li>2.3 ViewController中Block</li></ul></ul></div><p>本文将从以下两个层面解决<strong>iOS内存泄漏</strong>问题:</p><ul><li>内存泄漏排查方法(工具)</li><li>内存泄漏原因分析(解决方案)</li></ul>
<blockquote><p>在正式开始前,我们先区分两个基本概念:</p>
<p>内存泄漏(memory leak):是指申请的内存空间使用完毕之后<strong>未回收</strong>。 一次内存泄露危害可以忽略,但若一直泄漏,无论有多少内存,迟早都会被占用光,最终导致程序<code>crash</code>。(因此,开发中我们要尽量避免内存泄漏的出现)</p>
<p>内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用。 通俗理解就是内存不够用了,通常在运行大型应用或游戏时,应用或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。最终导致机器<code>重启</code>或者程序<code>crash</code>。</p></blockquote>
<p>简单来说:</p>
<table><tbody><tr><th>概念</th><th>区别说明</th></tr><tr><td>内存泄漏</td><td>供应方(操作系统)能提供给需求方(App)的内存越来越少。</td></tr><tr><td>内存溢出</td><td>需求方(App)需要的内存过大,超过供应方(操作系统)负载。</td></tr></tbody></table>
<p class="maodian"></p><h2>一、排查方法</h2>
<blockquote><p>我们知道,iOS开发有“ARC机制”帮忙管理内存,但在实际开发中,如果处理不好堆空间上的内存还是会存在内存泄漏的问题。如果内存泄漏严重,最终会导致程序的崩溃。</p></blockquote>
<p>首先,我们需要检查我们的App有没有内存泄漏,并且快速定位到内存泄漏的代码。目前比较常用的内存泄漏的排查方法有两种,都在Xcode中可以直接使用:</p>
<ul><li>第一种:静态分析方法(<code>Analyze</code>)</li><li>第二种:动态分析方法(<code>Instrument</code>工具库里的<code>Leaks</code>)。一般推荐使用第二种。</li></ul>
<p class="maodian"></p><h3>1.1 静态内存泄漏分析方法:</h3>
<ul><li><p>第一步:通过Xcode打开项目,然后点击Product->Analyze,开始进入静态内存泄漏分析。 如下图所示:</p></li></ul>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202307/2023713113207641.jpg" /></p>
<ul><li><p>第二步:等待分析结果。</p></li><li><p>第三步:根据分析的结果对可能造成内存泄漏的代码进行排查,如下图所示。</p></li></ul>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202307/2023713113207642.jpg" /></p>
<blockquote><p>PS:静态内存泄漏分析能发现大部分问题,但只是静态分析,并且并不准确,只是有可能发生内存泄漏。一些动态内存分配的情形并没有分析。如果需要更精准一些,那就要用到下面要介绍的动态内存泄漏分析方法(Instruments工具中的<code>Leaks</code>方法)进行排查。</p></blockquote>
<p class="maodian"></p><h3>1.2 动态内存泄漏分析方法:</h3>
<blockquote><p>静态内存泄漏分析不能把所有的内存泄漏排查出来,因为有的内存泄漏发生在运行时,当用户做某些操作时才发生内存泄漏。这是就要使用动态内存泄漏检测方法了。</p></blockquote>
<p>步骤如下:</p>
<ul><li>第一步:通过Xcode打开项目,然后点击Product->Profile,如下图所示:</li></ul>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202307/2023713113207643.jpg" /></p>
<ul><li>第二步:按上面操作,build成功后跳出Instruments工具,如上图右侧图所示。选择<code>Leaks</code>选项,点击右下角的【choose】按钮。如下图:</li></ul>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202307/2023713113207644.jpg" /></p>
<ul><li>第三步:这时候项目程序也在模拟器或手机上运行起来了,在手机或模拟器上对程序进行操作,工具显示效果如下:</li></ul>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202307/2023713113207645.jpg" /></p>
<p>点击左上角的红色圆点,这时项目开始启动了,由于<code>Leaks</code>是动态监测,所以手动进行一系列操作,可检查项目中是否存在内存泄漏问题。如图所示,橙色矩形框中所示绿色为正常,如果出现如右侧红色矩形框中显示红色,则表示出现内存泄漏。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202307/2023713113207646.jpg" /></p>
<p>选中Leaks Checks,在Details所在栏中选择CallTree,并且在右下角勾选<code>Invert Call Tree</code> 和<code>Hide System Libraries</code>,会发现显示若干行代码,双击即可跳转到出现内存泄漏的地方,修改即可。</p>
<p>举个例子:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202307/2023713113207647.jpg" /></p>
<p>PS:AFHTTPSessionManager内存泄漏是一个很常见的问题:解决方法有两种:</p>
<p><strong>第一种方案:把该manager封装成单例</strong></p>
<ul><li><p>解决理由:内存中的某一块固定的地址就用来存放manager,专门用来网络请求和释放。</p></li><li><p>方案代码:</p></li></ul>
<div class="jb51code"><pre class="brush:java;">static AFHTTPSessionManager *manager;
/* 封装成 单例会话管理者 */
+ (AFHTTPSessionManager *)sharedManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 初始化请求管理类
manager = ;
manager.requestSerializer = ;
// 设置15秒超时 - 取消请求
manager.requestSerializer.timeoutInterval = 15.0;
// 编码
manager.requestSerializer.stringEncoding = NSUTF8StringEncoding;
// 缓存策略
manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
manager.responseSerializer = ;
// 支持内容格式
manager.responseSerializer.acceptableContentTypes = ;
});
return manager;
}</pre></div>
<p><strong>问题:很明显,同一时刻只能有一个网络请求。异步会有问题。当两个线程同时申请manager对象时,肯定有一个manager申请不到,无法网络请求</strong></p>
<p><strong>第二种方案:在网络请求的block内把task取消掉</strong></p>
<p>无论是success,还是failure的回调都取消掉,当然在block外部需要弱化一下manager对象</p>
<div class="jb51code"><pre class="brush:java;">__weak typeof(manager) weakManager = manager;</pre></div>
<p>然后在两个回调方法里加上</p>
<div class="jb51code"><pre class="brush:java;">;</pre></div>
<blockquote><p>两种方案都可以解决内存泄漏问题。</p></blockquote>
<p class="maodian"></p><h2>二、内存泄漏的原因分析</h2>
<p>目前,在ARC环境下,导致内存泄漏的根本原因是代码中<strong>存在循环引用</strong>,从而导致一些内存无法释放,最终导致dealloc()方法无法被调用。主要原因大概有一下几种类型:</p>
<p class="maodian"></p><h3>2.1 ViewController中存在NSTimer</h3>
<p>如果你的ViewController中有NSTimer,那么你就要注意了,因为当你调用</p>
<div class="jb51code"><pre class="brush:plain;">[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateTime:)
userInfo:nil
repeats:YES];</pre></div>
<ul><li>理由:这时 <code>target: self</code>,增加了ViewController的<code>retain count</code>, 即<code>self</code>强引用<code>timer</code>,<code>timer</code>强引用<code>self</code>。造成循环引用。</li><li>解决方案:在恰当时机调用<code></code>即可。</li></ul>
<p class="maodian"></p><h3>2.2 ViewController中的代理delegate</h3>
<blockquote><p>代理在一般情况下,需要使用weak修饰。如果你这个VC需要外部传某个delegate进来,通过delegate+protocol的方式传参数给其他对象,那么这个delegate一定不要强引用,尽量使用weak修饰,否则你的VC会持续持有这个delegate,直到代理自身被释放。</p></blockquote>
<ul><li>理由:如果代理用<code>strong</code>修饰,ViewController(<code>self</code>)会强引用<code>View</code>,<code>View</code>强引用<code>delegate</code>,<code>delegate</code>内部强引用ViewController(<code>self</code>)。造成内存泄漏。</li><li>解决方案:代理尽量使用<code>weak</code>修饰。</li></ul>
<p>举个例子:代理一般用<code>weak</code>修饰,避免循环引用。</p>
<div class="jb51code"><pre class="brush:java;">@class QiAnimationButton;
@protocol QiAnimationButtonDelegate <NSObject>
@optional
- (void)animationButton:(QiAnimationButton *)button willStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didStartAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button willStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didStopAnimationWithCircleView:(QiCircleAnimationView *)circleView;
- (void)animationButton:(QiAnimationButton *)button didRevisedAnimationWithCircleView:(QiCircleAnimationView *)circleView;
@end
@interface QiAnimationButton : UIButton
@property (nonatomic, weak) id <QiAnimationButtonDelegate> delegate;
- (void)startAnimation; //!< 开始动画
- (void)stopAnimation; //!< 结束动画
- (void)reverseAnimation; //!< 最后的修改动画</pre></div>
<p class="maodian"></p><h3>2.3 ViewController中Block</h3>
<blockquote><p>在我们日常开发中,如果block使用不当,很容易导致内存泄漏。</p></blockquote>
<ul><li>理由:如果<code>block</code>被当前ViewController(<code>self</code>)持有,这时,如果block内部再持有ViewController(<code>self</code>),就会造成循环引用。</li><li>解决方案:在<code>block</code>外部对<strong>弱化</strong><code>self</code>,再在block内部<strong>强化</strong>已经弱化的<code>weakSelf</code></li></ul>
<p>For Example:</p>
<div class="jb51code"><pre class="brush:java;"> __weak typeof(self) weakSelf = self;
[self.operationQueue addOperationWithBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (completionHandler) {
KTVHCLogDataStorage(@"serial reader async end, %@", request.URLString);
completionHandler();
}
}];</pre></div>
<p>以上就是iOS 内存泄漏排查方法及原因分析的详细内容,更多关于iOS 内存泄漏的资料请关注琼殿技术社区其它相关文章!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>IOS内存泄漏检查方法及重写MLeakFinder</li><li>iOS中wkwebView内存泄漏与循环引用问题详解</li><li>iOS WKWebView中MessageHandler内存泄漏问题的完美解决过程</li><li>IOS 常见内存泄漏以及解决方案</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]