一、明确两点
1.Block可以访问Block函数以及语法作用域以内的外部变量。也就是说:一个函数里定义了一个block,这个block可以访问该函数的内部变量(当然还包括静态,全局变量),即block可以使用和本身定义范围相同的变量。
2.Block其实是特殊的Objective-C对象,可以使用copy、release等来管理内存,但和一般的NSObject的管理方式有些不同,稍后会说明。
二、Block语法
Block很像函数指针,这从Block的语法上就可以看出。
Block的原型:
返回值 (^名称)(参数列表)
Block的定义
^ 返回值类型 (参数列表) { 表达式 }
其中返回值类型和参数列表都可以省略,最简单的Block就是:
^{ ; };
一般的定义就是:
返回值 (^名称)(参数列表) = ^(参数列表){代码段};
为了方便通常使用typedef定义:
typedef void (^blk) (void);
三、Block存储域
Block能够截获自动变量,自动变量的当前值会被拷贝到栈上作为常量,此时不能在Block内对自动变量进行赋值操作,如果有这种需求,则需要该变量是:
1.静态变量
2.全局变量
3.或者使用__block修饰符
根据Block中是否引用了自动变量,可以将Block存储区域分类:
1.__NSStackBlock__存储在栈上
2.__NSGlobalBlock__存储在全局数据区域(和全局变量一样)
3.__NSMallocBlock__存储在堆上
没有引用自动变量或者在全局作用域的Block为__NSGlobalBlock__,其他的基本上都是__NSStackBlock__。对__NSStackBlock__执行copy操作会生成__NSMallocBlock__。
一般来说出问题的Block大部分都是__NSStackBlock__,超过了__NSStackBlock__的作用域__NSStackBlock__就会销毁。
四、对Block执行retain,copy方法的效果
Block是C语言的扩展,C语法也可以使用Block的语法,对应的C语言使用Block_copy、Block_release。
无论是__NSStackBlock__,还是__NSGlobalBlock__,执行retain都不起作用。而__NSMallocBlock__执行retain引用计数+1。
对于copy操作,__NSStackBlock__会被复制到堆上得到一份新的__NSMallocBlock__,__NSStackBlock__还是存在的,而__NSGlobalBlock__执行copy操作不起作用。而对__NSMallocBlock__执行copy操作会引起引用计数加1。
五、什么时候要对__NSStackBlock__执行copy操作?
配置在栈上的Block也就是__NSStackBlock__类型的Block,如果其所属的变量作用域结束该Block就会废弃。这个时候如果继续使用该Block,就应该使用copy方法,将__NSStackBlock__拷贝为__NSMallocBlock__。当__NSMallocBlock__的引用计数变为0,该__NSMallocBlock__就会被释放。
如果是非ARC环境,需要显式的执行copy或者antorelease方法。
而当ARC有效的时候,实际上大部分情况下编译器已经为我们做好了,自动的将Block从栈上复制到堆上。包括以下几个情况:
1.Block作为返回值时,类似在非ARC的时候,对返回值Block执行[[returnedBlock copy] autorelease];
2.方法的参数中传递Block时
3.Cocoa框架中方法名中还有useringBlock等时
4.GCD相关的一系列API传递Block时
比如:[mutableAarry addObject:stackBlock];这段代码在非ARC环境下肯定有问题,而在ARC环境下方法参数中传递__NSStackBlock__会自动执行copy,所以就不会出现问题。
六、Block的循环引用
对于非ARC下,为了防止循环引用,我们使用__block来修饰在Block中使用的对象。
对于ARC下,为了防止循环引用,我们使用__weak来修饰在Block中使用的对象。
原理就是:ARC中,Block中如果引用了__strong修饰符的自动变量,则相当于Block对该对象的引用计数+1。
七、代码验证
验证__NSStackBlock__
我们往可变数组中添加一个__NSStackBlock__。
在MRC环境下,__NSStackBlock__在自身作用域结束后就从栈中释放,所以我们再次使用它的时候,程序就会发生崩溃。
在ARC环境下,编译器会自动对__NSStackBlock__执行copy,拷贝一份到堆上生成一个新的__NSMallocBlock__,我们再次使用它的时候,程序正常运行。
#import <Foundation/Foundation.h> void addBlockToArray(NSMutableArray *arrayM) { int a = 1; [arrayM addObject:^{ printf("%d\n",a); }]; } int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *arrayM = [NSMutableArray array]; addBlockToArray(arrayM); void (^block)() = [arrayM objectAtIndex:0]; block(); NSLog(@"%@",block); } return 0; }
验证__NSGlobalBlock__
我们往可变数组中添加一个__NSGlobalBlock__。
不管是在MRC还是ARC环境下,__NSGlobalBlock_都是存储在程序的数据区,所以程序在MRC或ARC环境下都能正常执行。
#import <Foundation/Foundation.h> void addBlockToArray(NSMutableArray *arrayM) { [arrayM addObject:^{ printf("global\n"); }]; } int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *arrayM = [NSMutableArray array]; addBlockToArray(arrayM); void (^block)() = [arrayM objectAtIndex:0]; block(); NSLog(@"%@",block); } return 0; }