iOS中block技术小结
aswu5131
8年前
<p>block是C语言级别的语法和运行时特性,应用到Objective-C中可以增强函数功能。在合适场景中灵活应用block技术,对实际开发大有裨益。</p> <p>block是对C语言中函数的扩展,除了函数中的代码,还包含变量的绑定。block有时也被称为闭包(closure),闭包就是一个函数,或者一个指向函数的指针,加上这个函数执行的非局部变量。通俗一点,就是闭包允许一个函数访问声明该函数运行上下文中的变量,甚至可以访问不同运行上文中的变量。</p> <p><strong>脚本语言:</strong></p> <p>function funA(callback){</p> <p>alert(callback());</p> <p>}</p> <p>function funB(){</p> <p>var str = "Hello World"; //函数funB的局部变量,函数funA的非局部变量</p> <p>funA(</p> <p>function(){</p> <p>return str;</p> <p>}</p> <p>);</p> <p>}</p> <p>通过上面的代码我们可以看出,按常规思维来说,变量str是函数funB的局部变量,作用域只在函数funB中,函数funA是无法访问到str的。但是上述代码示例中函数funA中的callback可以访问到str,就是因为闭包性。</p> <p>block实际上就是Objective-C语言对于闭包的实现。block配合dispatch_queue,可以方便地实现简单的多线程编程和异步编程。</p> <h2>block原型及定义</h2> <p>block本质上是和其他变量类似。不同的是block存储的数据是一个函数体。使用block时,你可以像调用其他标准函数一样,传入参数,并得到返回值。</p> <p>脱字符(^)是block的语法标记,按照我们熟悉的参数语法规约所定义的返回值以及block的主体(也就是可以执行的代码)。下图讲解了如何把block变量赋值给一个变量的语法:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/5dda40214a99073da24c879ca8e339f6.jpg"></p> <p>按照调用函数的方式调用block对象变量就可以了:</p> <p>int result = myBlock(4); //result是28</p> <h2>使用typedef关键字</h2> <p>由于block数据类型的语法会降低整个代码的阅读性,所以常使用typedef来定义block类型。</p> <p>typedef double (^Multiply2BlockRef)(double c, double d);</p> <p>这行语句定义了一个名为Multiply2BlockRef的block变量,它包含两个double类型参数并返回一个double类型数值。有了typedef定义,就可以像下面这样使用这个变量:</p> <p>Multiply2BlockRef multiply2 = ^(double c, double d) {</p> <p>return c * d;</p> <p>}</p> <p>printf(“%f”, multiply2(4, 5));</p> <h2>Block和变量</h2> <p>block被声明后会捕捉创建点时的状态。block可以访问函数用到的标准类型的变量:</p> <p>全局变量;</p> <p>全局函数(这个是可以调用);</p> <p>封闭范围内的变量;</p> <p>与block声明时同级别的__block变量(这是可以修改的);</p> <p>封闭范围内的非静态变量会被获取为常量;</p> <p>Objective-C对象;</p> <p>block内部变量;</p> <p>3.1 本地变量</p> <p><strong>本地变量就是与block在同一范围内声明的变量。</strong></p> <p>typedef double (^Multiply2BlockRef)(double c, double d);</p> <p>double a = 10, b = 10;</p> <p>Multiply2BlockRef multiply = ^(void) { return a * b;}</p> <p>a = 20;</p> <p>b = 20;</p> <p>NSLog(@“%f”, multiply());</p> <p>这个NSLog会输出什么值?400?不是,为什么?</p> <p>因为对于本地变量,block定义时copy变量的值,在block中作为常量使用,所以即使变量的值在block外改变,也不影响他在block中的值。NSLog只会输出100。</p> <p>3.2 全局变量</p> <p>全局变量或静态变量在内存中的地址是固定的,block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。</p> <p>3.3 Block变量</p> <p>本地变量会被block作为常量获取到,如果想要修改它们的值,必须将它们声明为可修改的,否则会编译出错。</p> <p>double c = 3;</p> <p>Multiply2BlockRef multiply2 = ^(double a, double b){ c = a * b; } // 编译会报错</p> <p>如果想要在block代码里修改本地变量,需要将变量标记为__block。基于之前的代码,给变量c添加__block关键字,如下:</p> <p>__block double c = 3;</p> <p>对于用__block修饰的外部变量引用,block是复制其引用地址来实现访问的。</p> <p>3.4Objective-C对象</p> <p>一般来说我们总会在设置block之后,在合适的时间回调block,而不希望回调block的时候block已经被释放了,所以我们需要对block进行copy,copy倒堆中,以便后用。</p> <p>当一个block被Copy的时候,如果你在block里进行了一些调用,那么将会有一个强引用指向这些调用方法的调用者,有两个规则:</p> <p>如果是通过引用来访问一个实例变量,那么将强引用至self;</p> <p>如果是通过值来访问一个实例变量,那么将直接强引用至这个“值”变量;</p> <p>苹果官方文档里有两个例子来说明这两种情况:</p> <p>dispatch_async(queue, ^{</p> <p>// instanceVariable is used by reference, a strong reference is made to self</p> <p>doSomethingWithObject(instanceVariable);</p> <p>});</p> <p>id localVariable = instanceVariable;</p> <p>dispatch_async(queue, ^{</p> <p>/*</p> <p>localVariable is used by value, a strong reference is made to localVariable</p> <p>(not to self)</p> <p>*/</p> <p>doSomethingWithObject(localVariable);</p> <p>});</p> <p>上面第一种情况相当于通过self.xxx来访问实例变量,所以强引用指向了self;第二种情况把实例变量变成了本地临时变量,强引用将直接指向这个本地的临时变量。有时第一种情况可能会造成循环引用(在后面的注意事项中会解释),要避免强引用到self的话,用__weak把self重新引用一下,在block中使用weakSelf就行了,比如:</p> <p>__weak UIViewController *weakSelf = self;</p> <h2>Block类型</h2> <p>block有几种不同的类型,这里列出常见的三种类型:</p> <p>_NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:</p> <p>int main()</p> <p>{</p> <p>^{ printf("Hello, World!\n"); } ();</p> <p>return 0;</p> <p>}</p> <p>_NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:</p> <p>int main()</p> <p>{</p> <p>char a = 'A';</p> <p>^{ printf("%c\n",a); } ();</p> <p>return 0;</p> <p>}</p> <p>_NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:</p> <p>void exampleB_addBlockToArray(NSMutableArray *array) {</p> <p>char b = 'B';</p> <p>[array addObject:^{</p> <p>printf("%c\n", b);</p> <p>}];</p> <p>}</p> <p>void exampleB() {</p> <p>NSMutableArray *array = [NSMutableArray array];</p> <p>exampleB_addBlockToArray(array);</p> <p>void (^block)() = [array objectAtIndex:0];</p> <p>block();</p> <p>}</p> <p>_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,可以理解为它在内存的text区。</p> <p>_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block有且只有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。</p> <p>_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。</p> <h2>使用block的注意事项</h2> <p><strong>避免在block里用self造成循环引用</strong></p> <p>block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增加1(MRC)。在ARC与MRC环境下对block使用不当都会引起循环引用问题。一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是block的这种循环引用会被编译器捕捉到并及时提醒。</p> <p>self.someBlock = ^(Type var) {</p> <p>[self doSomething];</p> <p>//或self.otherVar = XXX;</p> <p>//或_otherVar = ...</p> <p>};</p> <p>即使在block代码中没有显式地出现"self",也会出现循环引用!只要在block里用到了self所拥有的东西!</p> <p>对于这种情况,可以通过添加__weak声明(ARC)或者__block声明(MRC)去禁止block对self进行强引用或者强制增加引用计数。</p> <p>开发过程中该选择block还是delegate</p> <p>如果对象有超过一个以上不同的事件源,使用delegation;一般的delegate方法会有返回值;delegate的回调更多的面向过程,而block则是面向结果的。如果需要得到一条多步进程的通知,应该使用delegation。而只是希望得到请求的信息(或者获取信息时的错误提示),应该使用block。</p> <h2>总结</h2> <p>可见灵活安全地使用block,必定会使编码工作事半功倍。block经常被用作回调函数,取代传统的回调方式,可以使得编码时更顺畅,不用中途换到另一个地方写一个回调函数。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。block通常适用于以下场景:任务完成时回调;处理消息监听回调处理;错误回调处理;枚举回调;视图动画、变换;排序等。</p> <p> </p> <p>来自:http://www.jianshu.com/p/62ca84286507</p> <p> </p>