无论开发中还是面试中,都会面临对于block使用方面的理解。而约定成俗的使用让我们知其然而不知其所以然。现参考多方资料总结对于block的多层次理解。
一、理论
什么是闭包
计算机语言中、“闭包(Closure)是由函数和与其相关的引用环境组合而成的实体.” block就是OC对闭包的实现.,Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展.
将“函数、函数指针、闭包”三者对比起来理解,能加深对闭包的理解;
函数:具有特定功能的代码块;
函数指针:指向函数的指针;
闭包:除具备“函数和函数指针”的所有功能外, 还包括声明它的上下文(如作用域内的自由变量等).
闭包的用途
1、“惰性求值”特性可用作定义控制语句;
2、多函数使用同一个环境;
3、实现对象系统.
通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。
“典型实现方式是定义一个特殊的数据结构,保存了函数地址指针与闭包创建时的函数的词法环境(also lexical closures or function closures)。”
Block简介
1、block是将函数及其执行上下文封装起来的一个对象。
2、在block实现的内部,有很多变量,因为block也是一个对象。
3、其中包含了诸如isa指针,imp指针等对象变量,还有储存其截获变量的对象等。
定义和使用
block根据有无参数和有无返回值有以下几种简单使用方式
block是一种数据类型,可以使一段代码块变成一个变量,格式和函数很像.
1,无参无返回: void (^myBlock)() = ^(){ ...... };(无参的话前面小括号可以省略,后面分号不能少).
2,有参无返回: void (^myBlock)(int,int) = ^(int a, int b){ .......};(有参数的话,'='号后面的形参名不能省).
3,无参有返回: int (^myBlock)() = ^{.......return....};
4,有参有返回:int (^myBlock)(int , int) = ^(int a ,int b){ ....return a+b;};
不同于函数的是block可以在内部访问外部的变量,但是不能给外部变量重新赋值,因为在内部使用的外部变量是copy的新的外部变量,内存位置不一样.
在block内部也可以定义和外部同名的变量,这样就会屏蔽外部变量的作用域,内部无法使用外部变量.
在默认的情况下,内部不能修改外部变量.当给外部的局部变量加上__Block修饰词,则内部可以改变该变量.
在Objective-C语言中,一共有3种类型的block:
1、_NSConcreteGlobalBlock 保存在text段的全局的静态block,不会访问任何外部变量。
2、_NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
3、_NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁。
以下说明几点需要注意的:
1、NSConcreteGlobalBlock 是全局静态block,结构体存储在数据区。
2、常见的是有捕获外部变量的_NSConcreteStackBlock,需要注意的是如果这种类型的block 定义在函数内部,当函数执行完毕,退栈的时候会将该block结构体所占的内存空间释放掉,这样再引用的话会报错。
3、_NSConcreteMallocBlock 通常不会在源码中直接出现,OC ARC下会对_NSConcreteStackBlock 进行优化,将其copy到堆上,转换成_NSConcreteMallocBlock,所以无特殊处理,OC中将只会有1,3两种类型block
4、_NSConcreteStackBlock捕获的局部变量,如不加_block修饰符,将会把变量copy一份到其结构体中,所以才会在内部修改不影响外部变量,加_block修饰之后,结构体中会添加一个__Block_byref_i_0 的结构体,且复制的是变量地址,达到可以修改外部变量的效果
block的使用注意事项(面试常问情况)
在block内直接调用类的实例变量会使self(类的实例)引用计数加1, 这样可能会引起循环引用问题(可以用__weak或local-var处理);
使用null的block程序会crash. 使用前判断一下:if(blockVar) {//do something…};
在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。