简单理解 Block

HyeSimone 7年前
   <h2><strong>一、万年不变老问题:什么是 Block</strong></h2>    <p>Block 是一段代码块,可以简单的理解为带有自动变量的 匿名函数 , 自动变量 可以理解为 局部变量 , 匿名函数 就是没有名字的函数。 Block 可以像 函数 一样,传入 参数 ,得到 返回值 。</p>    <h2><strong>二、Block 的声明与定义</strong></h2>    <ol>     <li> <p>Block 变量的声明</p> <pre>  <code class="language-objectivec">int (^myBlock)(int);</code></pre> <p>上述代码使用操作符 ^ 声明了一个名为 myBlock 的变量。入参为 int 类型,返回值为 int 类型。</p> </li>     <li style="text-align:center"> <p>Block 的定义</p> <pre>  <code class="language-objectivec">myBlock = ^(int num) {       return num * 2;   };</code></pre> <p>^ 操作符表示 Block 语句的开始;</p> <p>() 中为参数列表;</p> <p>{} 中是实现的实现的实体;</p> <p>于是我们就知道上面的代码在说什么了:传入参数 num ,返回 int 类型的对象,赋值给 myBlock 。</p> <p>完整的分析可以看下图</p> <img src="https://simg.open-open.com/show/6b63b343641f75cd0f3455494c138a15.jpg"> <p>1.jpg</p> </li>     <li> <p>与函数的不同</p>      <ol>       <li>没有函数名;(匿名函数嘛~)</li>       <li>带有操作符 ^ ;</li>      </ol> </li>    </ol>    <h2><strong>三、Block 的使用</strong></h2>    <p>1. Block 的调用</p>    <p>好了,我们已经知道怎么声明及定义 Block ,那么怎么使用呢?超简单,上面说了 Block 与 函数很像,想想我们是怎么使用函数的?</p>    <pre>  <code class="language-objectivec">NSLog(@"================%d", myBlock(3));  //结果为6</code></pre>    <p>2. Block 作为函数参数</p>    <p>添加 typedef 关键字,声明一个 Block 类型变量。添加关键字的目的是,可以直接使用名称 nameBlock 。</p>    <pre>  <code class="language-objectivec">typedef void(^nameBlock)(NSString *name);</code></pre>    <p>将 nameBlock 作为入参,实现一个函数。</p>    <pre>  <code class="language-objectivec">- (void)nameFunction:(nameBlock)nameBlock {      nameBlock(@"小井");  }</code></pre>    <p>对于函数的使用,直接调用时:</p>    <pre>  <code class="language-objectivec">[self nameFunction:^(NSString *name) {      if (![name isEqualToString:@""]) {          NSLog(@"My name is %@", name);      }  }];  // 结果为 My name is 小井</code></pre>    <h2><strong>四、Block 中变量的修改</strong></h2>    <p>声明一个变量 temp , 声明一个 testBlock ,在 testBlock 的实现体中打印出变量 temp 的值。</p>    <pre>  <code class="language-objectivec">int temp = 0;  void (^testBlock)() = ^{      NSLog(@"temp = %d", temp);    };  temp = 1;  testBlock();  //结果 temp = 0</code></pre>    <p>从最后的打印结果可以看出,尽管在调用 testBlock 之前,对变量 temp 重新赋值为 1, 打印结果仍为 0。于是我们知道了:</p>    <p>Block 在访问外部变量时,会拷贝一份到自己的数据存储中。</p>    <p>为了证明我们的观点,分别在 testBlock 实现体内部和实现体外部打印下地址。</p>    <pre>  <code class="language-objectivec">int temp = 0;    void (^testBlock)() = ^{      NSLog(@"temp = %d", temp);      NSLog(@"内部 temp is %ld", &temp);  };  temp = 1;  NSLog(@"外部 temp is %ld", &temp);  testBlock();   //结果    // 外部 temp is 140734745021036   // temp = 0   // 内部 temp is 106102872376448</code></pre>    <p>果然,地址不一样了。</p>    <p>下面我们尝试在 testBlock 中修改变量 temp 的值,在 testBlock 的实现实体中,添加如下一句代码。</p>    <pre>  <code class="language-objectivec">temp = 2;</code></pre>    <p>会发现,编译器报错了。这是因为 Block 拷贝的变量值是 const 的,即,在 Block 内部不能随意修改。但是当我确实有这样的需求,希望在 Block 内部修改外部变量时,怎么办呢?当当当~~~,只需要在外部变量的声明之前加上 __block 关键字,就可以愉快的在 Block 内部修改变量的值了,我们试试。</p>    <pre>  <code class="language-objectivec">__block int temp = 0;    void (^testBlock)() = ^{      temp = 2;      NSLog(@"temp = %d", temp);      NSLog(@"内部 temp is %ld", &temp);  };  temp = 1;  NSLog(@"外部 temp is %ld", &temp);  testBlock();  // 结果  // 外部 temp is 106102872333208  // temp = 2  // 内部 temp is 106102872333208</code></pre>    <p>我们可以发现,在 testBlock 内部成功的修改了变量 temp 的值,并且,跟之前不一样的是,这次的变量地址也相同的。因为加入了 _block 修饰符后, Block 不再拷贝原变量,而是拷贝原变量的引用地址,即这次是把指针拷贝了过来,指针指向原变量地址</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e76202ca3428083a9635a10eba08a30d.jpg"></p>    <p style="text-align:center">2.jpg</p>    <h2><strong>五、Block 深坑之循环引用</strong></h2>    <p>block 的使用不当会造成循环引用,内存泄露。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d3fc4b5a0d62a6e40f82f67b940672ac.jpg"></p>    <p style="text-align:center">3.jpg</p>    <pre>  <code class="language-objectivec">typedef void (^block_t)(void);    @interface TestObject : NSObject {      block_t block_;  }  @end    @implementation TestObject    - (id)init {      self = [super init];      block_ = ^(void) {            NSLog(@"self = %@", self);      };      return self;  }</code></pre>    <p>上面的代码编译器会显示 warning</p>    <pre>  <code class="language-objectivec">Capturing ‘self’ strongly in this block is likely to lead to a retain cycle;</code></pre>    <p>block_ 是 self 的成员变量, self 持有 Block 的强引用。在 init 初始化方法中, Block 的实现体中,使用了 id 类型的 self , 赋值给了成员变量 block_ ,Block 语法自动由栈拷贝到堆,Block 持有了 self ,于是造成了循环引用。当 main 函数结束时,由于循环引用的存在,堆上的对象不能释放,造成了内存泄露。如下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5851a07069b5eb57a4ccc54a42451cce.jpg"></p>    <p style="text-align:center">4.jpg</p>    <p>解决 Block 的内存泄露有两种办法:</p>    <ol>     <li>使用 __block;</li>     <li>使用 __weak;</li>    </ol>    <p>先说使用 __weak :声明 __weak 属性的临时变量 temp , 并将 self 赋值给临时变量。</p>    <pre>  <code class="language-objectivec">- (id)init {      self = [super init];      id __weak temp = self;      block_ = ^(void) {          NSLog(@"self = %@", temp);      };      return self;  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d19031fbd7e602cde882c3e68c805c27.jpg"></p>    <p style="text-align:center">5.jpg</p>    <p>再说使用 __block 方法:</p>    <pre>  <code class="language-objectivec">- (id)init {        self = [super init];          __block id temp = self;        block_ = ^(void) {          NSLog(@"self = %@", temp);          temp = nil;      };      return self;  }</code></pre>    <p>可以分析出:</p>    <p>self 持有 Block ;</p>    <p>Block 持有 __block 变量;</p>    <p>__block 变量持有 self ;</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1fe5f91e8093039fe41ab2ea44c15ab9.jpg"></p>    <p style="text-align:center">6.jpg</p>    <p>从图上可以看出,还是会存在循环引用的。此时只需要显示的调用下 block_() 就能解决问题,因为在 Block 的执行体中, temp 变量被赋值为 nil , 对 self 的强引用失效,故解除了循环引用。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6d5d37b18125b3093c8924089bcef7f7.jpg"></p>    <p style="text-align:center">7.jpg</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/5371a9c27e8d</p>    <p> </p>