简单理解 Block
HyeSimone
8年前
<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>