Block简介
Block 对象是 C 级别的语法和运行时特性。它们和标准 C 函数很类似,但是除了可执行代码外,它们还可能包含了变量自动绑定(栈)或内存托管(堆)。所以一个 block 维护一个状态集(数据),它们可以在执行的时候用来影响程序行为。
blocks 来编写函数表达式,这些表达式可以作为 API 使用,或可选的存储,或被多个线程使用。Blocks 作为回调特别有用,
Blocks 通常代表一个很小、自包的代码片段。因此它们作为封装的工作单元在并 发执行,或在一个集合项上,或当其他操作完成时的回调的时候非常实用
Block 对象提供了一个使用 C 语言和 C 派生语言(如 Objective-C 和 C++)来创建 表达式作为一个特别(ad hoc)的函数。在其他语言和环境中,一个block对象有时候 被成为“闭包(closure)”。在这里,它们通常被口语化为”块(blocks)”,
一个 block 就是一个匿名的内联代码集合体:
和函数一样拥有参数类型
有推断和声明的返回类型
可以捕获它的声明所在相同作用域的状态,可以和其他定义在相同作用域范围的 blocks 进行共享更改
在相同作用域范围(栈帧)被销毁后持续共享和更改相同作用域范围(栈帧)的状态
Block创建和声明
声明和使用一个block:
使用^操作符来来声明一个 block 变量和指示 block 文本的开始。Block 本身的主体被{}包含着。
注意 block 可以使用相同作用域范围内定义的变量。
如果你声明一个 block 作为变量,你可以把它简单的作为一个函数使用
当你在多个地方使用同一个给定的签名的 block 时,这通常被认为是最佳的办法。
Typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock= ···
MyBlockType mySecondBlock=···
Blocks 还支持可变参数(…)。一个没有使用任何参数的 block 必须在参数列表 上面用 void 标明。
你可以把一个 block 引用强制转换为任意类型 的指针,反之亦然。但是你不能通过修饰符 * 来解引用一个 block,因此一个 block 的大小是无法在编译的时候计算的。
直接使用一个block
在很多情况下,你不需要声明一个 block 变量;相反你可以简单的写一个内联 (inline)的 block 文本,它需要作为一个参数使用。
在 Cocoa frameworks 里面有部分方法使用 block 作为参数,通常不是执行一个对 象的集合操作,就是在操作完成的时候作为回调使用。
__block变量
Blocks 的最大一个特色就是可以修改相同作用域的变量。你可以使用__block 存 储类型修饰符来给出信号要修改一个变量。
__block 变量保存在变量共享的作用域范围内,所有的 blocks 和 block 副本都声明 或创建在和变量的作用域相同范围内。所以,如果任何 blocks 副本声明在栈内并未超 出栈的结束时,该存储会让栈帧免于被破坏(比如封装为以后执行)。同一作用域范 围内给定的多个 block 可以同时使用一个共享变量。
作为一种优化,block存储在栈上面,就像blocks本身一样。如果使用Block_copy 拷贝了 block 的一个副本(或者在 Objective-C 里面给 block 发送了一条 copy 消息), 变量会被拷贝到堆上面。所以一个__block 变量的地址可以随时间推移而被更改。
使用__block 的变量有两个限制:它们不能是可变长的数组,并且它们不能是包 含有 C99 可变长度的数组变量的数据结构。
Block和变量
你可以引用三种标准类型的变量,就像你在函数里面引用那样:
全局变量,包括静态局部变量。
全局函数(在技术上而言这不是变量)。
封闭范围内的局部变量和参数。
Blocks 同样支持其他两种类型的变量:
在函数级别是__block变量。这些在block里面是可变的(和封闭范围),并任何引 用 block 的都被保存一份副本到堆里面。
引入const。
在 block 里面使用变量遵循以下规则:
全局变量可访问,包括在相同作用域范围内的静态变量。
传递给block的参数可访问(和函数的参数一样)。
程序里面属于同一作用域范围的堆(非静态的)变量作为const变量(即只读)。
它们的值在程序里面的 block 表达式内使用。在嵌套 block 里面,该值在最近的
封闭范围内被捕获。
属于同一作用域范围内并被__block存储修饰符标识的变量作为引用传递因此是可变的。
属于同一作用域范围内block的变量,就和函数的局部变量操作一样。
每次调用 block 都提供了变量的一个拷贝。这些变量可以作为 const 来使用,或在 block 封闭范围内作为引用变量。
对象(Object)和Block变量
在引用计数的环境里面,默认情况下当你在 block 里面引用一个 Objective-C 对象的时 候,该对象会被 retain。当你简单的引用了一个对象的实例变量时,它同样被 retain。 但是被__block 存储类型修饰符标记的对象变量不会被 retain.
如果你在实现方法的时候使用了 block,对象的内存管理规则更微妙: 如果你通过引用来访问一个实例变量,self 会被 retain。如果你通过值来访问一个实例变量,那么变量会被 retain。
当你拷贝一个 block 时,任何在该 block 里面对其他 blocks 的引用都会在需要的 时候被拷贝,即拷贝整个目录树(从顶部开始)。如果你有 block 变量并在该 block 里 面引用其他的 block,那么那个其他的 block 会被拷贝一份。
当你拷贝一个基于栈的 block 时,你会获得一个新的 block。但是如果你拷贝一个 基于堆的 block,你只是简单的递增了该 block 的引用数,并把原始的 block 作为函数 或方法的返回值。
Block总结
除了前面提到的block可以作为一个函数被调用,可以作为函数的参数,可以作为方法的参数。_block还可以修饰变量,用于在快中可以对变量操作。下面block还有几种用法和注意点。
拷贝Block
通常,你不需要 copy(或 retain)一个 block.在你希望 block 在它被声明的作用域 被销毁后继续使用的话,你子需要做一份拷贝。拷贝会把 block 移到堆里面。使用 Objective-C,你可以给一个 block 发送 copy、retain 和 release(或 autorelease)消息。 保证copy和release的平衡。
自己练习的小demon
#import "BlockViewController.h"
@interface BlockViewController ()
@end
@implementation BlockViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//block作为函数调用
int (^square)(int) = ^(int a){return a*a;};
int result = square(3);
NSLog(@"%d",result);
//block作为函数参数
//[self myFc];
//block作为方法参数
[self objcMethod:square];
//变量存取
[self para];
//_block修饰变量
[self blockM];
}
-(void)objcMethod:(int(^)(int))square{
if (square(3)>=8) {
NSLog(@"good");
}else NSLog(@"bad");
}
-(void)para{
//局部变量
int outA = 8;
int (^myPtr)(int) = ^(int a){ return outA + a;}; //block里面可以读取同一类型的outA的值
outA = 5; //在调用myPtr之前改变outA的值
int result = myPtr(3); // result的值仍然是11,并不是8
NSLog(@"result=%d", result);
//静态变量
static int outB = 8;
int (^myPtr1)(int) = ^(int a){return outB + a;};
outB = 5;
int result1 = myPtr1(3); //result的值是8,因为outC是static类型的变量
NSLog(@"result1=%d", result1);
//在block修改变量值
static int outC = 8;
int (^myPtr2)(int) = ^(int a){ outC = 5; return outC + a;};
int result2 = myPtr2(3); //result的值是8,因为outC是static类型的变量
NSLog(@"result2=%d", result2);
}
-(void)blockM{
__block int num = 5;
int (^myPtr)(int) = ^(int a){return num++;};
int (^myPtr2)(int) = ^(int a){return num++;};
int result = myPtr(0); //result的值为5,num的值为6
int result1 = myPtr2(0); //result的值为6,num的值为7
NSLog(@"result=%d result1=%d", result,result1);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
扩展:block 作为类的成员变量
可以用weak,不会将block从本身的栈区拷贝到堆区,而是让block继续在堆区自动管理。
也可以用copy,但不能用strong, block如果用到了self,就会retain self ,如果是strong的话,就造成了循环引用。 使用retain也可以,但是block的retain行为默认是用copy的行为实现的,因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。