iOS闭包


原文地址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属性得不到释放。分析其形成循环引用的原因(如下图):

image

可以简单说,这里形成了两个环:

  • 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的引用问题上就可以了。

分析:如下图所示:

image

所形成的环有:

  • 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下载地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值