摘选自:[http://www.jianshu.com/p/ee9756f3d5f6]
Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功能“Blocks”。从那开始,Block就出现在iOS和Mac系统各个API中,并被大家广泛使用。一句话来形容Blocks,带有自动变量(局部变量)的匿名函数。
Block在OC中的实现如下:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
从结构图中很容易看到isa,所以OC处理Block是按照对象来处理的。在iOS中,isa常见的就是_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock这3种(另外只在GC环境下还有3种使用的_NSConcreteFinalizingBlock,_NSConcreteAutoBlock,_NSConcreteWeakBlockVariable,本文暂不谈论这3种,有兴趣的看看官方文档)
1.从捕获外部变量的角度上来看
_NSConcreteStackBlock:
只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。
_NSConcreteMallocBlock:
有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制
_NSConcreteGlobalBlock:
没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。
2.从持有对象的角度上来看:
- _NSConcreteStackBlock是不持有对象的。
//以下是在MRC下执行的
NSObject * obj = [[NSObject alloc]init];
NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
void (^myBlock)(void) = ^{
NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
};
NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
myBlock();
输出:
1.Block外 obj = 1
2.Block外 obj = 1
Block中 obj = 1
- _NSConcreteMallocBlock是持有对象的。
//以下是在MRC下执行的
NSObject * obj = [[NSObject alloc]init];
NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
void (^myBlock)(void) = [^{
NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
}copy];
NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
myBlock();
[myBlock release];
NSLog(@"3.Block外 obj = %lu",(unsigned long)obj.retainCount);
输出:
1.Block外 obj = 1
2.Block外 obj = 2
Block中 obj = 2
3.Block外 obj = 1
- _NSConcreteGlobalBlock也不持有对象
//以下是在MRC下执行的
void (^myBlock)(void) = ^{
NSObject * obj = [[NSObject alloc]init];
NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
};
myBlock();
输出:
Block 中 obj = 1
由于_NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上。
1.手动调用copy
2.Block是函数的返回值
3.Block被强引用,Block被赋值给__strong或者id类型
4.调用系统API入参中含有usingBlcok的方法
以上4种情况,系统都会默认调用copy方法把Block赋复制
但是当Block为函数参数的时候,就需要我们手动的copy一份到堆上了。这里除去系统的API我们不需要管,比如GCD等方法中本身带usingBlock的方法,其他我们自定义的方法传递Block为参数的时候都需要手动copy一份到堆上。
copy函数把Block从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。
Block中__block实现原理
1.普通非对象的变量
__block的变量也被转化成了一个结构体
2.对象的变量
在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!
最后
关于Block捕获外部变量有很多用途,用途也很广,只有弄清了捕获变量和持有的变量的概念以后,之后才能清楚的解决Block循环引用的问题。
再次回到文章开头,5种变量,自动变量,函数参数 ,静态变量,静态全局变量,全局变量,如果严格的来说,捕获是必须在Block结构体__main_block_impl_0里面有成员变量的话,Block能捕获的变量就只有带有自动变量和静态变量了。捕获进Block的对象会被Block持有。
对于非对象的变量来说,
自动变量的值,被copy进了Block,不带__block的自动变量只能在里面被访问,并不能改变值。
带__block的自动变量 和 静态变量 就是直接地址访问。所以在Block里面可以直接改变变量的值。
而剩下的静态全局变量,全局变量,函数参数,也是可以在直接在Block中改变变量值的,但是他们并没有变成Block结构体__main_block_impl_0的成员变量,因为他们的作用域大,所以可以直接更改他们的值。
值得注意的是,静态全局变量,全局变量,函数参数他们并不会被Block持有,也就是说不会增加retainCount值。
对于对象来说,
在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象。
ARC环境下,Block也是存在__NSStackBlock的时候的
在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSConcreteMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 NSStackBlock 类型的 block 转换为 NSMallocBlock 类型。
举例如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block int temp = 10;
NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});
return 0;
}
输出
<__NSStackBlock__: 0x7fff5fbff768>