一、block是不是对象
苹果的ObjectiveC官方文档中在“Working with Blocks”明确说明:
“ Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary. ”
可见, Block是Objective C语言中的对象
苹果在block的文档中也提过这么一句:
“ As an optimization, block storage starts out on the stack—just like blocks themselves do. ”
Clang的文档中也有说明:
“ The initial allocation is done on the stack, but the runtime provides a Block_copy function ”
凭这一点,我们就可以回答剩下的两个问题。 Block对象与一般的类实例对象有所不同,一个主要的区别就是分配的位置不同,block默认在栈上分配,一般类的实例对象在堆上分配。
二、block可访问变量的范围
一个函数里定义了个block,这个block可以访问该函数的内部变量(当然还包括静态,全局变量),即block可以使用和本身定义范围相同的变量。
另外,block有截获局部变量的功能,什么叫截获局部变量呢?就是当block中访问局部变量时,会把这个局部变量拷贝到堆区。当block执行时,用到的还是这个拷贝到堆区的变量,不会受外面修改局部变量的影响。举个例子:
int a = 100;
void(^blcokOne)() = ^{
NSLog(@"%d",a);
};
a = 200;
blcokOne();
NSLog(@"------>%d",a);
我们发现局部变量a的值确实是改变了,但是block中引用的a值却没有改变,就是上面的原理。
也正是因为有上面的原理,所以就有了block中不能修改局部变量的值(因为block中访问的局部变量是被拷贝到堆区的,可以认为是常量,当然就不可以修改了)。如果想修改,定义时要用__block修饰。即__block int a = 100
。当然全局变量和静态变量是可以修改的。
三、Block存储域
根据存储域不同,block可以分为三种类型,分别是
- __NSStackBlock__ (存储在栈上)
- __NSMallocBlock__ (存储在堆上)
- __NSGlobalBlock__ (存储在全局数据区域)
没有引用局部变量或者声明为全局变量的Block为NSGlobalBlock,其他的基本上都是NSStackBlock。对NSStackBlock执行copy操作会生成NSMallocBlock。一般来说出问题的Block大部分都是NSMallocBlock,超过了NSMallocBlock的作用域NSMallocBlock就会销毁。
四、Block执行copy、retain的情形
无论是NSStackBlock,还是NSGlobalBlock,执行retain都不起作用。而NSMallocBlock执行retain引用计数+1。
在栈上调用copy那么复制到堆上,在全局block调用copy什么也不做,在堆上调用block 引用计数增加。
如果是非ARC环境,需要显式的执行copy或者antorelease方法。而当ARC有效的时候,实际上大部分情况下编译器已经为我们做好了,自动的将Block从栈上复制到堆上。包括以下几个情况:
1,Block作为返回值时
类似在非ARC的时候,对返回值Block执行[[returnedBlock copy] autorelease];
2,方法的参数中传递Block时
3,Cocoa框架中方法名中含有useringBlock等时
4,GCD相关的一系列API传递Block时。