*注:解释内容主要参考《Objective-C 高级编程》
1.介绍:
Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数。
Blocks的语法:^ 返回值类型 参数列表 表达式
例如:
^int (int c){return c+1;}
其中,
返回值类型 是可以省略的,会按return类型返回,如果不需要参数,那么
参数列表 也可以省略,如:
^{printf("hello");}
2.Block类型变量
int (^blk)(int) = ^(int c){return c+1;};
int x = blk(5);
NSLog(@"x = %d",x);
Block值
^(
int
c){
return
c+
1
;} 被赋值给了变量blk中,blk即为Block变量。
利用typedef定义Block别名:
typedef int (^blk_1)(int);
blk_1 blkk = ^(int x){return x+3;};
NSLog(@"x = %d",blkk(3));
意指用blk_1来代替int ^(int);这种块,之后调用赋值什么的会更舒服。
3.截获自动变量值
int x = 1;
void (^blk)(void) = ^{
NSLog(@"zhi = %d",x);
};
x = 6;
blk();
以上代码输出结果 zhi = 1;可见在Block截获的是自动变量x的值,之后改变x,并不会对块的执行结果有影响。并且我们如果想在块中改变x的值是无法通过编译的。如果我们想在块中改变x的值,需要用__block说明符
4.__block说明符
__block int x = 1;
void (^blk)(void) = ^{
x = 6;
};
blk();
NSLog(@"x = %d",x);
输出结果为 x = 6
用__block修饰x后,便可以在块中改变截取的自动变量x,那么: __block int x = 1;
void (^blk)(void) = ^{
NSLog(@"x = %d",x);
};
x = 7;
blk();
输出结果为 x = 7
这里思考,如果我们不用__block,截取OC对象,会怎么样:
NSString *age = @"26";
NSMutableDictionary *dic =[[NSMutableDictionary alloc]init];
[dic setObject:age forKey:@"age"];
void (^blk)(void) = ^{
NSString *a = [dic objectForKey:@"age"];
NSLog(@"age = %@",a);
};
NSString *age2 = [dic objectForKey:@"age"];
age2 = @"18";
[dic removeObjectForKey:@"age"];
[dic setObject:age2 forKey:@"age"];
blk();
输出结果为 age = 18
这里我们初始化了一个dic,里面有一个key为@"age"的NSString,初始值为26,在块中输出字典中字符串的值,然后在块执行前,改变dic中字符串。由此我们联想:
NSString *age = @"26";
NSMutableDictionary *dic =[[NSMutableDictionary alloc]init];
[dic setObject:age forKey:@"age"];
void (^blk)(void) = ^{
NSString *age2 = [dic objectForKey:@"age"];
age2 = @"18";
[dic removeObjectForKey:@"age"];
[dic setObject:age2 forKey:@"age"];
};
blk();
NSString *a = [dic objectForKey:@"age"];
NSLog(@"age = %@",a);
输出 age = 18
所以在块中也能改变dic的值。这里我们分析,我们用块截取的是NSMutableDIcitionary的实例指针,不能对其赋值,但是可以使用它,即可以向其中添加改变对象。
*这里我们要注意:
const char text[] = "HelloWorld";
const char *text1 = "HelloWorld";
void (^blk)(void) = ^{
printf("%c",text1[1]);
};
blk();
我们可以使用截取*text1,但是text[]不可以,因为截取自动变量的方法并没有实现对C语言数组的接获,应该用指针方法解决。
4.Blocks的实现
那么这种截取自动变量以及__block修饰变量,背后是怎样实现的呢,本文简单介绍一下有关截取变量这里,至于Blocks实现的详细解析放在后续文章中。
我们利用clang的指令转变一下我们的文件,打开终端,利用 clang -rewrite-objc ‘文件名’ 对我们的文件进行操作,我们的文件内容如下:
int main(){
int i = 10;
int (^blk)(void) = ^{
int x = i+1;
return x;
};
int jieguo = blk();
return 0;
}
声明一个简单的块,截获了自动变量 int i,我们用clang指令操作它得到.cpp文件:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//块中函数
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
int x = i+1;
return x;
}
由于转化后代码太多,就只截取了一部分。可以看到结构体构造函数初始化 i(_i) , 让i = _i,这是一个copy的过程,所以外部改变自动变量的值,不会影响到块中的值。
如果使用__block修饰的话:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int x) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
int y = x + (i->__forwarding->i);
return y;
}
可以看到,在构造函数中,i(_i->__forwarding) , 这里用一个结构体__Block_byref_i_0,以指针的方式去保存自动变量,保存了变量的地址,那么后续改变自动变量,块中的值也会相应改变。
有关块循环引用的问题,及各种使用会在后面文章中介绍。