Block概要和使用规范
Block定义
Block是带有自动变量的匿名函数,这里的自动变量是局部变量,匿名函数是说不需要知道该函数的名称也可以调用。无需提前声明或命名就能作为参数传递给其他函数或方法,或者作为变量保存和执行
Block的语法
完整形式的Block语法和一般的C语言函数相比,只有两点不同。第一点是没有函数名,因为它是匿名函数。第二点是返回值类型前带有"^"(插入记号)记号。下面是Block语法格式:
^ 返回值类型 参数列表 表达式
objective-c
^int (int count){return count + 1;}
Block语法可以省略返回值类型
objective-c
^(int count){return count + 1;}
如果不使用参数,Block语法还可以省略参数列表
objective-c
^void (void){printf("Blocks\n");}
在C语言中可以将函数地址赋值给函数指针的类型变量中,同样在Block语法下,可将Block语法赋值给Block类型的变量中。示例如下:
objective-c
int (^blk)(int) = ^(int count) {return count + 1;};
int (^blk_t)(int);
blk_t = blk;
Block类型变量声明
返回参数 (^变量名称)(接受参数);
同时使用typedef重命名Block类型,因为Block类型变量和平时的使用类型相同,为了方便我们使用,我们通常都是用typedef来重命名Block。
objective-c
typedef 返回参数 (^该Block的名称)(接收参数)
截获自动变量值
在定义Block的时候,其之前的变量都会被该Block所截获,该Block后接着再改变变量的值然后再调用,其Block中所捕获的值不会受到改变。
objective-c
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt, val);
};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
_ _Block修饰符
自动变量截获只能保存执行Block语法瞬间的值并且保存后就不能修改这个值,如果要修改这个自动变量的值就需要在在声明时给这个自动变量附加_ _Block说明符。
objective-c
int main(int argc, const char * argv[]) {
__block int val = 10;
__block const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt, val);
val = 20;
};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
printf(fmt, val);
return 0;
}
-
_ _block修饰符只能于局部变量,不能用全局变量或静态变量。
-
在Block部使用__ block修的变量时,需要注意循环引用的问题如果Block被强引用并且同时引用了__ block修饰的变,可能会导致环引用,造成内存漏。为了避免这种情况,可以在Block内部使用weakSelf来弱引用self,或者使用__ weak饰符来修饰__ block变量。
-
当__ block修饰的变量Block内部被修改,外部变量的值会被修改。这意味在Block执行完后,外部变量的值将保持被修改后的状态。
-
在使用ARC(动引用计数)的情况下,__ block饰符会自动处理内存管理。但是,在非ARC环境下,你需要手动处理__block变量的内存管理,确保Block执行完毕后释放它们。
截获的自动变量
向Block截获的自动变量赋值会报错,如果调用变更该对象的方法则不会产生编译错误。
用OC中的NSMutableArray来说,截获它其实就是截获该类对象用的结构体实例指针,就是说你只要不改变其指针的指向和其指针的值,他就不会出错。
objective-c
int main(int argc, const char * argv[]) {
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
blk();
return 0;
}
如果这样写就改变了array的初始地址,所以他就会报错。
objective-c
int main(int argc, const char * argv[]) {
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
array = [[NSMutableArray alloc] init];
};
blk();
return 0;
}
还有需要注意的是在现在的Block中,截获自动变量的方法并没有实现对C语言数组的截获
block的属性修饰词是copy
因为Block的内存地址显示在栈区,栈区的特点就是创建的对象随时销毁,一旦销毁后续再次调用空对象就会造成程序崩溃。对Block进行copy操作之后,block存在堆区,所以在使用Block属性的时候Copy修饰。
block一旦没有进行copy操作,就不会在堆上。MRC 下 block 如果没有 copy 到堆上,值捕获不会对外部变量引用。 虽然 ARC 环境 strong 也可以修饰 Block,那是因为编译器会对 strong 修饰的 block 也会进行一次 copy 操作。
block在调用前需要进行判空
block的判空判断的是block变量有没有指向实际的Block对象
下面举个例子:
objective-c
int (^blk)(int) = ^(int count) {return count + 1;};
判断blk
这个变量有没有指向^(int count) {return count + 1;}
这个代码块对象。
下面是一个block为空的例子:
objective-c
int (^blk)(int) ;
可以看到blk并没有指向实际的Block对象,此时的block就是空的
当block为空的时候,CPU会去访问地址address=0x10,接着会报EXC_BAD_ACCESS错误。
Block的本质是一个结构体,其代码如下:
objective-c
struct __block_impl {
void *isa; //类型
int Flags; //标识字段
int Reserved; //保留字段
void *FuncPtr; //函数指针,这个会指向编译器给我们生成的下面的静态函数__main_block_func_0(实际执行的函数,也就是block中花括号里面的代码内容。)
};
0x10是十六进制,也就是struct基地址后的第16个字节,其中void *类型占8个字节,int类型占4个字节,所以0x10的地址就是FuncPtr的地址,而address=0x10的问题也正是对值为nil的block强行调用导致的。
调用Block的时候会去访问Block结构体中的FuncPtr指针,因为Block为空,所以此时的FuncPtr指针所指的位置是无效的,当访问这个无效的地址的时候就会报错