原文地址http://blog.csdn.net/woaifen3344/article/details/50689546
前言
本篇文章精讲iOS开发中使用Block时一定要注意内存管理问题,很容易造成循环引用。本篇文章的目标是帮助大家快速掌握使用block的技巧。
我相信大家都觉得使用block给开发带来了多大的便利,但是有很多开发者对block内存管理掌握得不够好,导致经常出现循环引用的问题。对于新手来说,出现循环引用时,是很难去查找的,因此通过Leaks不一定能检测出来,更重要的还是要靠自己的分析来推断出来。
声景一:Controller之间block传值
现在,我们声明两个控制器类,一个叫ViewController,另一个叫HYBAController。其中,ViewController有一个按钮,点击时会push到HYBAController下。
先看HYBAController:
<code class="hljs objectivec has-numbering"><span class="hljs-comment">// 公开了一个方法</span> - (instancetype)initWithCallback:(HYBCallbackBlock)callback; <span class="hljs-comment">// 非公开的属性,这里放出来只是告诉大家,HYBAController会对这个属性强引用</span> <span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, copy) HYBCallbackBlock callbackBlock;</code>
下面分几种小场景来看看循环引用问题:
<code class="hljs objectivec has-numbering"><span class="hljs-class"><span class="hljs-keyword">@interface</span> <span class="hljs-title">ViewController</span> ()</span> <span class="hljs-comment">// 引用按钮只是为了测试</span> <span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">strong</span>) <span class="hljs-built_in">UIButton</span> *button; <span class="hljs-comment">// 只是为了测试内存问题,引用之。在开发中,有很多时候我们是</span> <span class="hljs-comment">// 需要引用另一个控制器的,因此这里模拟之。</span> <span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">strong</span>) HYBAController *vc; <span class="hljs-keyword">@end</span> <span class="hljs-comment">// 点击button时</span> - (<span class="hljs-keyword">void</span>)goToNext { HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ [<span class="hljs-keyword">self</span><span class="hljs-variable">.button</span> setTitleColor:[<span class="hljs-built_in">UIColor</span> greenColor] forState:UIControlStateNormal]; }]; <span class="hljs-keyword">self</span><span class="hljs-variable">.vc</span> = vc; [<span class="hljs-keyword">self</span><span class="hljs-variable">.navigationController</span> pushViewController:vc animated:<span class="hljs-literal">YES</span>]; }</code>
现在看ViewController这里,这里在block的地方形成了循环引用,因此vc属性得不到释放。分析其形成循环引用的原因(如下图):
可以简单说,这里形成了两个环:
- ViewController->强引用了属性vc->强引用了callback->强引用了ViewController
- ViewController->强引用了属性vc->强引用了callback->强引用了ViewController的属性button
对于此这问题,我们要解决内存循环引用问题,可以这么解:
不声明vc属性或者将vc属性声明为weak引用的类型,在callback回调处,将self.button改成weakSelf.button,也就是让callback这个block对viewcontroller改成弱引用。如就是改成如下,内存就可以正常释放了:
<code class="hljs objectivec has-numbering">- (<span class="hljs-keyword">void</span>)goToNext { __<span class="hljs-keyword">weak</span> __typeof(<span class="hljs-keyword">self</span>) weakSelf = <span class="hljs-keyword">self</span>; HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ [weakSelf<span class="hljs-variable">.button</span> setTitleColor:[<span class="hljs-built_in">UIColor</span> greenColor] forState:UIControlStateNormal]; }]; <span class="hljs-comment">// self.vc = vc;</span> [<span class="hljs-keyword">self</span><span class="hljs-variable">.navigationController</span> pushViewController:vc animated:<span class="hljs-literal">YES</span>]; }</code>
笔者尝试过使用Leaks检测内存泄露,但是全是通过,一个绿色的勾,让你以为内存处理得很好了,实际上内存并得不到释放。
针对这种场景,给大家提点建议:
- 在控制器的生命周期viewDidAppear里打印日志:
<code class="hljs objectivec has-numbering">- (<span class="hljs-keyword">void</span>)viewDidAppear:(<span class="hljs-built_in">BOOL</span>)animated { [<span class="hljs-keyword">super</span> viewDidAppear:animated]; <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"进入控制器:%@"</span>, [[<span class="hljs-keyword">self</span> class] description]); }</code>
- 在控制器的生命周期dealloc里打印日志:
<code class="hljs ruby has-numbering">- (void)dealloc { <span class="hljs-constant">NSLog</span>(@<span class="hljs-string">"控制器被dealloc: %@"</span>, [[<span class="hljs-keyword">self</span> <span class="hljs-class"><span class="hljs-keyword">class</span>] <span class="hljs-title">description</span>]);</span> }</code>
这样的话,只要日志没有打印出来,说明内存得不到释放,就需要学会分析内存引用问题了。
场景二:Controller与View之间Block传值
我们先定义一个view,用于与Controller交互。当点击view的按钮时,就会通过block回调给controller,也就反馈到控制器了,并将对应的数据传给控制器以记录:
<code class="hljs objectivec has-numbering"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">void</span>(^HYBFeedbackBlock)(<span class="hljs-keyword">id</span> model); <span class="hljs-class"><span class="hljs-keyword">@interface</span> <span class="hljs-title">HYBAView</span> : <span class="hljs-title">UIView</span></span> - (instancetype)initWithBlock:(HYBFeedbackBlock)block; <span class="hljs-keyword">@end</span> <span class="hljs-class"><span class="hljs-keyword">@interface</span> <span class="hljs-title">HYBAView</span> ()</span> <span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, copy) HYBFeedbackBlock block; <span class="hljs-keyword">@end</span> <span class="hljs-class"><span class="hljs-keyword">@implementation</span> <span class="hljs-title">HYBAView</span></span> - (<span class="hljs-keyword">void</span>)dealloc { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"dealloc: %@"</span>, [[<span class="hljs-keyword">self</span> class] description]); } - (instancetype)initWithBlock:(HYBFeedbackBlock)block { <span class="hljs-keyword">if</span> (<span class="hljs-keyword">self</span> = [<span class="hljs-keyword">super</span> init]) { <span class="hljs-keyword">self</span><span class="hljs-variable">.block</span> = block; <span class="hljs-built_in">UIButton</span> *button = [<span class="hljs-built_in">UIButton</span> buttonWithType:UIButtonTypeCustom]; [button setTitle:@<span class="hljs-string">"反馈给controller"</span> forState:UIControlStateNormal]; button<span class="hljs-variable">.frame</span> = CGRectMake(<span class="hljs-number">50</span>, <span class="hljs-number">200</span>, <span class="hljs-number">200</span>, <span class="hljs-number">45</span>); button<span class="hljs-variable">.backgroundColor</span> = [<span class="hljs-built_in">UIColor</span> redColor]; [button setTitleColor:[<span class="hljs-built_in">UIColor</span> yellowColor] forState:UIControlStateNormal]; [button addTarget:<span class="hljs-keyword">self</span> action:<span class="hljs-keyword">@selector</span>(feedback) forControlEvents:UIControlEventTouchUpInside]; [<span class="hljs-keyword">self</span> addSubview:button]; } <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>; } - (<span class="hljs-keyword">void</span>)feedback { <span class="hljs-keyword">if</span> (<span class="hljs-keyword">self</span><span class="hljs-variable">.block</span>) { <span class="hljs-comment">// 传模型回去,这里没有数据,假设传nil</span> <span class="hljs-keyword">self</span><span class="hljs-variable">.block</span>(<span class="hljs-literal">nil</span>); } } <span class="hljs-keyword">@end</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li></ul>
接下来看HYBAController,增加了两个属性,在viewDidLoad时,创建了aView属性:
<code class="hljs objectivec has-numbering"><span class="hljs-class"><span class="hljs-keyword">@interface</span> <span class="hljs-title">HYBAController</span>()</span> <span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, copy) HYBCallbackBlock callbackBlock; <span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">strong</span>) HYBAView *aView; <span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>, <span class="hljs-keyword">strong</span>) <span class="hljs-keyword">id</span> currentModel; <span class="hljs-keyword">@end</span> <span class="hljs-class"><span class="hljs-keyword">@implementation</span> <span class="hljs-title">HYBAController</span></span> - (instancetype)initWithCallback:(HYBCallbackBlock)callback { <span class="hljs-keyword">if</span> (<span class="hljs-keyword">self</span> = [<span class="hljs-keyword">super</span> init]) { <span class="hljs-keyword">self</span><span class="hljs-variable">.callbackBlock</span> = callback; } <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>; } - (<span class="hljs-keyword">void</span>)viewDidLoad { [<span class="hljs-keyword">super</span> viewDidLoad]; <span class="hljs-keyword">self</span><span class="hljs-variable">.title</span> = @<span class="hljs-string">"HYBAController"</span>; <span class="hljs-keyword">self</span><span class="hljs-variable">.view</span><span class="hljs-variable">.backgroundColor</span> = [<span class="hljs-built_in">UIColor</span> whiteColor]; <span class="hljs-keyword">self</span><span class="hljs-variable">.aView</span> = [[HYBAView alloc] initWithBlock:^(<span class="hljs-keyword">id</span> model) { <span class="hljs-comment">// 假设要更新model</span> <span class="hljs-keyword">self</span><span class="hljs-variable">.currentModel</span> = model; }]; <span class="hljs-comment">// 假设占满全屏</span> <span class="hljs-keyword">self</span><span class="hljs-variable">.aView</span><span class="hljs-variable">.frame</span> = <span class="hljs-keyword">self</span><span class="hljs-variable">.view</span><span class="hljs-variable">.bounds</span>; [<span class="hljs-keyword">self</span><span class="hljs-variable">.view</span> addSubview:<span class="hljs-keyword">self</span><span class="hljs-variable">.aView</span>]; <span class="hljs-keyword">self</span><span class="hljs-variable">.aView</span><span class="hljs-variable">.backgroundColor</span> = [<span class="hljs-built_in">UIColor</span> whiteColor]; } - (<span class="hljs-keyword">void</span>)viewDidAppear:(<span class="hljs-built_in">BOOL</span>)animated { [<span class="hljs-keyword">super</span> viewDidAppear:animated]; <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"进入控制器:%@"</span>, [[<span class="hljs-keyword">self</span> class] description]); } - (<span class="hljs-keyword">void</span>)dealloc { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"控制器被dealloc: %@"</span>, [[<span class="hljs-keyword">self</span> class] description]); } <span class="hljs-keyword">@end</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li></ul>
关于上一场景所讲的循环引用已经解决了,因此我们这一小节的重点就放在controller与view的引用问题上就可以了。
分析:如下图所示:
所形成的环有:
- vc->aView->block->vc(self)
- vc->aView->block->vc.currentModel
解决的办法可以是:在创建aView时,block内对currentModel的引用改成弱引用:
<code class="hljs objectivec has-numbering">__<span class="hljs-keyword">weak</span> __typeof(<span class="hljs-keyword">self</span>) weakSelf = <span class="hljs-keyword">self</span>; <span class="hljs-keyword">self</span><span class="hljs-variable">.aView</span> = [[HYBAView alloc] initWithBlock:^(<span class="hljs-keyword">id</span> model) { <span class="hljs-comment">// 假设要更新model</span> weakSelf<span class="hljs-variable">.currentModel</span> = model; }];</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>
我见过很多类似这样的代码,直接使用成员变量,而不是属性,然后他们以为这样就不会引用self,也就是控制器,从而不形成环:
<code class="hljs objectivec has-numbering"><span class="hljs-keyword">self</span><span class="hljs-variable">.aView</span> = [[HYBAView alloc] initWithBlock:^(<span class="hljs-keyword">id</span> model) { <span class="hljs-comment">// 假设要更新model</span> _currentModel = model; }];</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
这是错误的理解,当我们引用了_currentModel时,它是控制器的成员变量,因此也就引用了控制器。要解决此问题,也是要改成弱引用:
<code class="hljs objectivec has-numbering">__block __<span class="hljs-keyword">weak</span> __typeof(_currentModel) weakModel = _currentModel; <span class="hljs-keyword">self</span><span class="hljs-variable">.aView</span> = [[HYBAView alloc] initWithBlock:^(<span class="hljs-keyword">id</span> model) { <span class="hljs-comment">// 假设要更新model</span> weakModel = model; }];</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>
这里还要加上__block哦!
模拟循环引用
假设下面如此写代码,是否出现内存得不到释放问题?(其中,controller属性都是强引用声明的)
<code class="hljs perl has-numbering"><span class="hljs-variable">@autoreleasepool</span> { A <span class="hljs-variable">*aVC</span> = [[A alloc] init]; B <span class="hljs-variable">*bVC</span> = [[B allcok] init]; aVC.controller = bVC; bVC.controller = aVC; }</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>
分析:
aVC->强引用了bVC->强引用了aVC,因此形成了一个环,导致内存得不到释放。
写在最后
本篇文章就讲这么多吧,写本篇文章的目的是教大家如何分析内存是否形成环,只要懂得了如何去分析内存是否循环引用了,那么在开发时一定会特别注意内存管理问题,而且查找内存相关的问题的bug时,也比较轻松。
源代码
本篇写了个小demo来测试的,如果只看文章不是很明白的话,如果下载demo来运行看一看,可以帮助您加深对block内存引用问题的理解。
下载地址:标哥的GITHUB下载地址