Block
Block作为日常开发中必备的一种开发技巧,通过日常的使用和学习,在此对 Block的基本概念,使用语法和 对于外部变量的使用三方面做一个学习总结。
1.什么是Blocks
什么是blocks,blocks是c语言的一种扩充功能。可以解释为“带有自动变量的匿名函数”。
匿名函数
匿名函数就是指没有名称的函数。正常的函数表达式一般为:
返回值类型 函数名 返回值 {
}
void func () {
}
而当我门需要调用这个函数时,是必须要知道函数名的。通常调用函数的方式有2种:
1 函数名()
2 函数指针的方式调用
// 1 函数名调用
func();
// 2 函数指针调用
void(*funcptr)() = &func;
funcptr();
而blocks的函数表达式并没有带函数名:
^ 返回值 参数 {
}
^ void(){
};
把blocks赋值给block类型的参数
void(^blk)() = ^ void(){
};
调用blocks
blk();
或者
^ void(){
}();
可以发现blocks函数与常见的函数的区别 是没有了函数名和 最前面加上了** ^ *符号。而block类型的变量和函数指针的区别只是把函数指针的号换成了^。
截获自动变量
带有自动变量其实可以解释为能够截取自动变量的值。通常的变量类型有
- 局部变量(自动变量)
- 全局变量
- 全局静态变量
- 静态变量
能够在函数的多次调用的时候传递的变量值只有:
- 全局变量
- 全局静态变量
- 静态变量
void test() {
int a = 10;
void(^blk)() = ^void(){
printf("a = %d",a);
};
a = 20;
blk();
}
函数最后的打印结果为*** a = 10 ***。到这里可以解释出blocks为带有(截获)自动变量i的匿名函数。
2.Block语法
block函数
关于block函数可以表示为 ^ 返回值类型 参数 表达式。
而以下4种方式也是可以的
1.^ 返回值类型 参数 表达式
^int(){
return 1;
};
2.^ 参数 表达式 (省略返回值类型)
^(){
return 1;
};
3.^ 返回值类型 表达式 (在没有参数的情况下省略参数)
^int{
return 1;
};
4.^ 表达式 (返回值类型和参数 都可以省略)
^{
return 1;
};
block类型的变量
在之前介绍过block类型的变量,将block函数可以赋值给block类型变量。它和函数指针十分相似。
// 函数指针
void func() {
}
void (*funcptr)() = &func;
//block类型变量
void (^blk)() = ^{
};
在日常开发中,我门会经常使用到block,关于block 它可以作为
- block类型变量
- 属性
- 方法参数
- 方法返回值
// 1 block作为变量
void (^blk)() = ^{
};
// 2 block作为属性
@property(nonatomic,copy) void(^blk)();
//3 block作为方法参数
- (void)methodUseBlock:(void (^)())blk {
}
//4 block作为方法返回值
- (void(^)())methodReturnBlock {
return ^{};
}
平时为了更方便的使用block,将这种复杂的记述方式便于使用,可以借助typedef来使用block
typedef void (^blkType)();
通过使用typedef,再来看下实现之前的block场景
typedef void (^blkType)();
// 1 block作为变量
blkType blk = ^void(){
};
// 2 block作为属性
@property(nonatomic,copy) blkType blk;
//3 block作为方法参数
- (void)methodUseBlock:(blkType)blk {
}
//4 block作为方法返回值
- (blkType)methodReturnBlock {
return ^{};
}
平时方便查阅的话,可以到这里看下 http://fuckingblocksyntax.com/
3.截获的外部变量
保存定义时的值
在之前介绍block函数概念的时候,说了block能够截获外部变量。而且是在定义block函数的时候截获了外部的变量.对于局部变量来说在定义的block函数之后,改变局部变量的值。block内部使用的局部变量的值依然是block函数定义时的值。如下面的代码 函数最后的执行结果依然是 *** a = 10 ***.
void test() {
int a = 10;
void(^blk)() = ^void(){
printf("a = %d",a);
};
a = 20;
blk();
}
在block内部改变外部变量
如果block内部使用了外部变量,是不允许在block内部修改截获的变量的值的。所以下面的这段代码就是编译不过的。因为它改变了截获的外部变量a的值。
int a = 10;
void(^blk)() = ^{
a = 20;
};
但是下面的这段代码却是可以正常编译的。
NSMutableArray *tempArray = [NSMutableArray array];
void(^blk)() = ^{
[tempArray arrayByAddingObject:[[NSObject alloc]init]];
};
在这段代码中,blk确实截获了外部变量 tempArray这个数组,在block内部也向数组里增加了一个元素。为什么它能编译通过呢。其实block内部不允许改变截获的外部变量应该更严格的说是不允许改变外部变量的内存地址。
NSMutableArray *tempArray = [NSMutableArray array];
NSLog(@"原始的内存地址 %p",&tempArray);
[tempArray arrayByAddingObject:[[NSObject alloc]init]];
NSLog(@"添加元素后的内存地址 %p",&tempArray);
打印结果
2016-12-18 16:40:19.565 block[8358:803598] 原始的内存地址 0x7fff5778cac8
2016-12-18 16:40:19.565 block[8358:803598] 添加元素后的内存地址 0x7fff5778cac8
可以看出内存地址并没有改变,而如果对对象进行赋值操作,则会和之前一样直接编译错误。因为改变了外部变量的内存地址。
NSMutableArray *tempArray = [NSMutableArray array];
void(^blk)() = ^{
tempArray = [NSMutableArray arrayWithObjects:[[NSObject alloc]init], nil];
};
__block说明符
既然在block内部无法直接修改截获的外部变量的值,而如果我门需要这样的功能的话,可以通过对外部变量加上 _ _ block修饰符则能实现在block内部修改外部变量的值。如下代码通过对外部变量a加上 _ _block 修饰符,就算在block内部修改了a的值,也能正常编译。
__block int a = 10;
void(^blk)() = ^{
a = 20;
};
blk();