Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数;
匿名函数就是没有名称的函数。
C语言中的变量(根据作用域不同区分的):
1、自动变量(局部变量);
2、函数的参数;
3、静态变量(静态局部变量);
4、静态全局变量;
5、全局变量;
其中,静态变量(静态局部变量)、静态全局变量、全局变量是可以在函数的多次调用之间传递的变量。
Block语法,形式如下:(^ 返回值类型 参数列表 表达式)
^(void) (int event)
{
Printf(“buttonId:%d event=%d\n”, i, event);
}
Block语法与一般C语言函数定义相比,有两点不同:
1、没有函数名;
2、带^;
^:插入记号,便于查找
Block语法可以省略返回值类型;也可以省略参数列表类型;
省略返回值类型和参数列表类型后的语法格式为:^{}
函数指针的声明示例:
int (*funcptr) (int) = &func;
Block类型变量的示例:
int (^blk) (int)
Block变量和一般的C语言变量完全相同,可作为以下用途使用:
1、自动变量;
2、函数参数;
3、静态(局部)变量;
4、静态全局变量;
5、全局变量;
demo如下:
int i = 10;
NSString *str = @"hello";
void (^blk) (void) = ^{
NSLog(@"str = %@, i = %d", str, i);
};
str = @"I'm not ok";
i = 11;
blk();
NSLog(@"end");
最后打印的日志如下:
从上面的日志可以看出:Block语法使用的是之前定义时的自动变量的值,也就是截获自动变量的值,保存起来,在执行时使用。即使在block定义和执行期间修改过这些自动变量的值,也不会影响,它还是执行之前定义时所截获的自动变量值。
这就是自动变量值的截获。
int i = 10;
NSString *str = @"hello";
void (^blk) (void) = ^{
i = 12;
NSLog(@"str = %@, i = %d", str, i);
};
str = @"I'm not ok";
i = 11;
blk();
NSLog(@"end");
这个时候,i = 12会报错:
Variable is not assignable (missing __block type specifier)
如果将i声明为__block int i = 10;,就可以实现在block中对i进行赋值。
这种变量称为__block变量。
避免循环引用的方法有三种:
1、使用__block
2、使用__weak
3、使用__unsafe_unretained
使用__block修饰符的优点:
1、可以动态的控制对象的持有周期;(在执行block时,可动态的决定是都将nil或者其他对象赋值给__block变量)
2、在ARC和MRC都可以使用;
3、可以修饰对象,也可以修饰基本数据类型;
使用__block修饰符的缺点:
1、为了避免循环引用,必须执行Block;如果只执行了Block语法,却不执行Block的使用时,无法避免循环使用。
__weak修饰的自动变量:不能在Block中被赋值;只能修饰对象,不能修饰基本数据类型。
unsafe_unretained是在iOS4以及OS X Snow Leopard的环境下,也就是不能使用weak的环境下使用
ARC无效时,
Block从栈复制到堆,是通过copy实例方法实现;
手动释放Block,通过release实例方法来来释放;
在堆上,可通过retain实例方法持有;
而在栈上,使用retain方法持有是无效的;
因此推荐使用copy实例方法来持有Block,copy方法可实现将Block从栈复制到堆上。
默认情况下,Block创建时是在栈上的。
ARC无效时,__block可以被用来避免Block的循环使用。
因为,当Block从栈复制到堆上时,如果Block中使用的自动变量,有block说明符,那么就不会被retain。如果Block中使用的自动变量,没有block说明符,那么就会被retain。
注意:__block修饰符在ARC有效和无效时,区别很大,因此使用需注意。
首先,Block也是OC对象。
Block一共有三种类型:
1)、全局Block( _NSGlobalBlock):设置在程序的数据区(.data区);
2)、栈中的Block( _NSStackBlock):设置在栈上的;
3)、堆中的Block( _NSMallocBlock):设置在由malloc函数分配的内存块(堆)中。
1)、全局Block对象
有两种情况:
(1)在记述全局变量的地方存在Block语法:因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获,因此Block语法的内容也不会依赖于执行时的状态,就可以设置在与全局变量相同的数据区域,因此成为全局Block对象;(不截获)
(2)Block语法中的表达式,可能会截获自动变量的值,但是只要不使用截获的自动变量,这样的Block也是全局Block;(截获但不使用)
2)、栈中的Block
除了上面两种情况之外生成的Block都是_NSStackBlock对象,设置在栈上。
3)、堆中的Block
配置在全局变量上的Block,从变量的作用域外也可以通过指针安全的使用。
设置在栈上的Block,如果其所属的变量的作用域结束,这个Block也会被废弃。block变量也是配置在栈上的,如果其所属的变量的作用域结束,这个block变量也会被废弃。
Block提供了将Block和__block变量从栈上复制到堆上的方法来解决问题,这样“即使Block语法记述的变量作用域结束,堆上的Block也可以继续存在”。
在ARC有效时,编译器大多数情况下会恰当的进行判断,自动生成将Block从栈上复制到堆上的代码。比如,将Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码。
但是有些情况下,需要手动的使用copy方法生成代码,来将Block从栈上复制到堆上。
这些不能自动生成复制代码的情况有:
1、通过方法或函数的参数传递Block时,需要手动复制。
但是如果在方法或者函数中适当的复制了传递过来的参数,就不必在调用之前手动复制了。
比如在Cocoa框架的方法名中含有usingBlock的方法(如NSArray类的enumerateObjectsUsingBlock方法),再比如Grand Central Dispatch的API(如dispatch_async函数)。
如果不想判断这些情况,可以在所有情况下都进行copy,但是Block从栈上复制到堆上是非常消耗CPU的。
对于已经在堆上的Block或者全局Block调用copy方法,不会引起任何问题。
对于全局的Block进行copy时,什么也不做;
对于堆上的Block进行copy时,Block的引用计数加1.
网友评论