代码块(block)
block块定义
-
当前多线程编程的核心技术之一就是“块”,块是一种可以在C、C++、OC代码中使用的一种“闭包”,借用此机制,可将代码块像对象一样传递,在定义块的范围内,可以访问到块的全部变量。
-
块可以定义在栈中、堆中、全局块等。
-
在定义声明块变量和实现块变量的开头位置使用^幂操作符,与函数体一样,代码块的实现放在{ }中。
-
定义关系表示:
<return_type> (^block_name)(list of arguments)=^(arguments){body;};
int(^Mul_block)(int number1,int number2)=^(int number){number = number1*number2; return number;};
-
等号前面是代码块的定义,等号后面是代码块的实现内容
-
块的强大之处是:在声明它的范围里,所有的变量均可以为其所捕获。
使用block块
-
int result = Mul_block(5,10);
在调用时,不需要^符号。 -
__block代码块可以访问与它相同的有效范围内声明的变量,也就是说block可以访问与它同时创建的有效变量,默认情况下,块捕获的变量,是不可以在块里进行修改的,若在声明变量时加_block修饰符,就可以在块内修改。
int c = 9 ; c = Mul_block(2,5)
此时编译器会报错:Variable is not assignable(missing _block type specifier)
__block int c = 9 ; c = Mul_block(4,5)
有些变量是无法声明为__block类型的,它有两个限制:
1)没有长度可变的数组
2)没有包含可变长度数字的结构体
-
变量和block拥有相同的有效范围,block在定义时会复制并保存引用变量的当前状态
typedef double (^Multiply_block) (void); int a = 10,b = 100; Multiply_block multiply= ^(void){return a*b }; print(@"%f",multiply()); a = 10,b = 10; print(@"%f",multiply());
此时两个multiply打印的值是相同的均是1000,因为block在定义时就保存了a=10,b=100的变量值,如果把变量标为全局变量(静态):static int a = 10,b =100;,可改变其输出值。 -
使用block块时通常不需要创建一个代码块常量,而是在代码中内联代码块的内容。
-
为常用的块类型创建typedef,可以使块变量用起来更加简单,在定义新类型时也要遵循块的命名规则。这样做的好处是,如果要重构的代码使用了块类型的某个别名,那么只需要修改相应typedef中的块签名即可,无需改动其他的typedef。
块所捕获的变量
-
如果块定义在类的实例方法中,除了可以捕获所有的实例变量外,还可以直接使用self变量,且可以修改实例变量,无需加__block声明。
-
**保留环:**如果块捕获了self变量,就等同于捕获了self对象,在捕获的同时,块会将其保留,如果self所指代的对象同时也保留了块,就会导致保留环的出现。此时一定要找一个适当的时机清理掉环中某一个引用,就能解除保留环,如果保留环无法打破,内存就会泄露。
-
如果块捕获的变量也是对象,那么系统会自动保留它,系统在释放块的时候,会将其一并释放。块本身可视为对象,也有引用计数,当最后一个引用块的指针移走之后,块就会被回收,回收时释放块捕获的变量,平衡捕获时的保留操作。
-
块本身也是一个对象,在存放块对象的内存区域里,存放着块对象正常运转所需的所有信息。每个块都有一个descriptor变量,该变量是指向块结构体的指针,声明了块对象的总体大小,块还会把它所捕获的所有变量都拷贝一份,并保存到descriptor变量后的内存空间。拷贝的并不是对象本身,而是指向对象的指针变量。
-
在定义块时,块所占的内存区域是在栈中,由于块只在它定义的范围有效,离开的相应范围内,编译器有可能把分配给块的内存覆盖掉,可能导致程序崩溃。所以可以把块对象从栈到堆内存中,在调用块对象时使用copy消息拷贝一下。一旦复制到堆上,块就变成了可以使用引用计数的对象了。
-
全局块是一种声明在全局内存里且不需要每次使用时创建在栈中。全局块不会被系统回收,相当于一个单例。