1. 前言
Block:带有自动变量(局部变量)的匿名函数。它是C语言的扩充功能。之所以是拓展,是因为C语言不允许存在这样匿名函数。
1.1 匿名函数
匿名函数是指不带函数名称函数。C语言中,函数是怎样的呢?类似这样:
int func(int count);
调用的时候:
int result = func(10);
func就是它的函数名。也可以通过指针调用函数,看起来没用到函数名:
int result = (*funcptr)(10);
实际,在赋值给函数指针时,必须通过函数的名称才能获得该函数的地址。完整的步骤应该是:
int (*funcptr)(int) = &func; int result = (*funcptr)(10);
而通过Block,就能够使用匿名函数,即不带函数名称的函数。
1.2 带有自动变量
关于“带有自动变量(局部变量)”的含义,这是因为Block拥有捕获外部变量的功能。在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态。
捕获外部变量,看一个经典block面试题:
int val = 10; void (^blk)(void) = ^{ printf("val=%d
",val);
};
val = 2;
blk();
上面这段代码,输出值是:val = 10,而不是2。
block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝;换句话说block截获自动变量的瞬时值;或者block捕获的是自动变量的副本。
由于block捕获了自动变量的瞬时值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。
所以,上面的面试题的结果是2不是10。
解决block不能修改自动变量的值,这一问题的另外一个办法是使用__block修饰符。
__block int val = 10; void (^blk)(void) = ^{printf("val=%d
",val);};
val = 2;
blk();
上面的代码,跟第一个代码段相比只是多了一个__block修饰符。但是输出结果确是2。
2. Block语法大全
约定:用法中的符号含义列举如下:
-
return_type表示返回的对象/关键字等(可以是void,并省略)
-
blockName表示block的名称
-
var_type表示参数的类型(可以是void,并省略)
-
varName表示参数名称
2.1 Block声明及定义语法,及其变形
(1) 标准声明与定义
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
(2) 当返回类型为void
void (^blockName)(var_type) = ^void (var_type varName) { // ... };
blockName(var);
可省略写成
void (^blockName)(var_type) = ^(var_type varName) { // ... };
blockName(var);
(3) 当参数类型为void
return_type (^blockName)(void) = ^return_type (void) { // ... };
blockName();
可省略写成
return_type (^blockName)(void) = ^return_type { // ... };
blockName();
(4) 当返回类型和参数类型都为void
void (^blockName)(void) = ^void (void) { // ... };
blockName();
可省略写成
void (^blockName)(void) = ^{ // ... };
blockName();
(5) 匿名Block
Block实现时,等号右边就是一个匿名Block,它没有blockName,称之为匿名Block:
^return_type (var_type varName)
{ //... };
2.2 typedef简化Block的声明
利用typedef简化Block的声明:
- 声明
typedef return_type (^BlockTypeName)(var_type);
- 例子1:作属性
//声明 typedef void(^ClickBlock)(NSInteger index); //block属性 @property (nonatomic, copy) ClickBlock imageClickBlock;
- 例子2:作方法参数
//声明 typedef void (^handleBlock)(); //block作参数 - (void)requestForRefuseOrAccept:(MessageBtnType)msgBtnType messageModel:(MessageModel *)msgModel handle:(handleBlock)handle{
...
2.3 Block的常见用法
2.3.1 局部位置声明一个Block型的变量
- 位置
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
- 例子
void (^globalBlockInMemory)(int number) = ^(int number){ printf("%d
",number);
};
globalBlockInMemory(90);
2.3.2 @interface位置声明一个Block型的属性
- 位置
@property(nonatomic, copy)return_type (^blockName) (var_type);
- 例子
//按钮点击Block @property (nonatomic, copy) void (^btnClickedBlock)(UIButton *sender);
2.3.3 在定义方法时,声明Block型的形参
- 用法
- (void)yourMethod:(return_type (^)(var_type))blockName;
- 例子
UIView+AddClickedEvent.h
- (void)addClickedBlock:(void(^)(id obj))clickedAction;
2.3.4 在调用如上方法时,Block作实参
- 例子
UIView+AddClickedEvent.m
- (void)addClickedBlock:(void(^)(id obj))clickedAction{ self.clickedAction = clickedAction; // :先判断当前是否有交互事件,如果没有的话。。。所有gesture的交互事件都会被添加进gestureRecognizers中 if (![self gestureRecognizers]) { self.userInteractionEnabled = YES; // :添加单击事件 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
[self addGestureRecognizer:tap];
}
}
- (void)tap{ if (self.clickedAction) { self.clickedAction(self);
}
}
2.4 Block的少见用法
2.4.1 Block的内联用法
这种形式并不常用,匿名Block声明后立即被调用:
^return_type (var_type varName)
{ //... }(var);
2.4.2 Block的递归调用
Block内部调用自身,递归调用是很多算法基础,特别是在无法提前预知循环终止条件的情况下。注意:由于Block内部引用了自身,这里必须使用__block避免循环引用问题。
__block return_type (^blockName)(var_type) = [^return_type (var_type varName)
{ if (returnCondition)
{
blockName = nil; return;
} // ... // 【递归调用】 blockName(varName);
} copy];
【初次调用】
blockName(varValue);
2.4.3 Block作为返回值
方法的返回值是一个Block,可用于一些“工厂模式”的方法中: