Blocks是C语言的扩充。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。Block底层实现是以C语言结构体实现的,在OC中Block即为Objective-C对象。
所谓“带有自动变量”意味着在执行Block语法时,Block语法表达式所使用的自动变量被保存到Block的结构体实例中。本文主要对从Block内部访问不同种类变量的一些特性进行探究。
- 截获局部变量(截获自动变量值)
程序运行过程中局部变量被保存在栈存储区上,Block语法表达式所使用的局部变量的值被保存到Block的结构体实例中。简单来说,可以理解为默认情况下Block截获局部变量的方式为值传递 。
截获局部变量值的代码如下:
int
main(){
int val = 10 ;
const char * fmt = "val = %d\n" ;
void (^blk)( void ) = ^{ printf (fmt,val);};
val = 2 ;
blk();
return 0 ;
}
int val = 10 ;
const char * fmt = "val = %d\n" ;
void (^blk)( void ) = ^{ printf (fmt,val);};
val = 2 ;
blk();
return 0 ;
}
该源代码的执行结果为:
val =
10
该源代码中,Block语法表达式截获它之前声明的自动变量fmt和val。Blocks中,Block表达式截获所使用自动变量的值,即保存该变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值页不会影响Block执行时的自动变量的值。还有一点需要注意的是,在Block内部无法改变截获变量的值,否则程序会报错。
- 截获静态局部变量
程序运行过程中,静态局部变量保存在静态数据存储区。在执行Block语法时,Block语法表达式所使用的静态局部变量的指针会被保存到Block的结构体中。简单来说,可以理解为Block截获静态局部变量的方式为地址传递。
截获静态局部变量的代码如下:
int main(){
static int static_val = 10;
void (^blk)(void) = ^{
printf("print in block static_val = %d\n",static_val);
static_val = 5;
};
static_val = 2;
blk();
printf("static_val = %d",static_val);
}
static int static_val = 10;
void (^blk)(void) = ^{
printf("print in block static_val = %d\n",static_val);
static_val = 5;
};
static_val = 2;
blk();
printf("static_val = %d",static_val);
}
该源代码的执行结果为:
print in block static_val = 2
static_val = 5
static_val = 5
Block在执行时,通过存储的静态局部变量的指针来访问静态局部变量可以获取静态局部变量的当前值,并且可以在Block内部通过指针来改变静态变量的值。
- 截获全局变量/静态全局变量
程序运行过程中,全局变量/静态全局变量保存在静态数据存储区。前面提到Block的底层实现是基于C语言的,从Block中访问全局变量/静态全局变量与C语言中访问并没有什么不同。Block不需存储该全局变量/静态全局变量的指针或值。
由于全局变量与静态全局变量的区别仅仅在于作用域的不同,所以这里只以静态全局变量作为演示:
static
int
static_global_val =
2
;
int main(){
void (^blk)( void ) = ^{
printf ( "print in block static_global_val = %d\n" , static_global_val );
static_global_val = 5 ;
};
static_global_val = 2 ;
blk();
printf ( "static_global_val = %d" , static_global_val );
}
int main(){
void (^blk)( void ) = ^{
printf ( "print in block static_global_val = %d\n" , static_global_val );
static_global_val = 5 ;
};
static_global_val = 2 ;
blk();
printf ( "static_global_val = %d" , static_global_val );
}
该源代码的执行结果位:
print in block static_global_val = 2
static_global_val = 5
static_global_val = 5
Block在执行时,可以直接访问全局变量/静态全局变量的方式且与C语言的执行结果是一致的。
- __block说明符
前面提到Block只会保存局部变量在执行Block语法时的瞬时值。保存后就不能改写该值(但是可以使用该值,比如Block语法截获了NSMutableArray对象就可以调用该对象方法向数组里添加或删除元素),若想在Block语法中改写截获的局部变量的值,需要在该自动变量上附加__block说明符。而且对于__block修饰符修饰的变量,在执行Block语法后,改写变量值会影响到Block执行时的自动变量的值,改写自动变量值的代码如下:
int
main(){
__block int val = 10 ;
void (^blk)( void ) = ^{
printf ( "print in block val = %d\n" ,val);
val = 5 ;
};
val = 2 ;
blk();
printf ( "val = %d" ,val);
}
__block int val = 10 ;
void (^blk)( void ) = ^{
printf ( "print in block val = %d\n" ,val);
val = 5 ;
};
val = 2 ;
blk();
printf ( "val = %d" ,val);
}
该源代码的执行结果位:
print in block val = 2
val = 5
val = 5
使用附有__block说明符的自动变量可在Block语法中赋值,方便起见,在本文接下来的讨论中改变了称之为“__block变量”。
虽然__block变量和静态局部变量都可以实现在Block内部改变变量的值以及使用Block时获取变量的实时值,但是,在实际开发中还是推荐使用__block变量来实现该功能。其主要原因有两点:
- 静态局部变量保存在静态存储区,程序结束运行才会被释放。
- Block语法对__block变量进行了较为复杂的处理,确保了在不同存储区的Block实例能够访问到同一份拷贝的变量(例如栈存储区Block被拷贝到堆存储区后,某些情况下还是会访问到栈存储区的Block,此时就需要确保两份存储区上的Block实例能够访问到同一份拷贝的变量)。