前文中所列举的是最简单的block,方便先理解block的结构,下面讲解一下在block中使用外部变量与block的作用域。
1.截获自动变量
OC源码:
int i = 1;
void (^bBlock)() = ^{
NSLog(@"%d",i);
};
i=2;
bBlock();//输出1,说明了在block声明时已经将自动变量值复制到了block变量中,所以以后再修改被截获的值时也无法影响到block的执行部分了
转换后的C源码:
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 void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy截获自动变量
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_107bae_mi_0,i);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int i = 1;
void (*bBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
i=2;
((void (*)(__block_impl *))((__block_impl *)bBlock)->FuncPtr)((__block_impl *)bBlock);//6.block调用
//(*bBlock->impl.FuncPtr)(bBlock)
}
return 0;
}
根据与前文中的代码比较,可以发现,block的执行函数中调用了外部变量i,转换后的C源码中,在构造__main_block_impl_0结构体时,就已经将i的值传递进去了,所以并不是在执行block时才去获取的i值,而是声明的时候就已经将引用的外部变量值copy进block了。所以也可以解释为什么在block声明后修改了i的值但是输出的block结果还是之前获取到的i值了。这就是截获自动变量的概念。
如果试图在block中修改截取到的自动变量的值,如:
int i = 1;
void (^bBlock)() = ^{
i = i + 1;
NSLog(@"%d",i);
};
编译器会给出
这个错误,提示缺少了_ _block修饰符,有关__block修饰符的稍后在讲,现在需要明确的就是block可以截获自动变量的值,且无法修改。
但对于这三种变量是可以修改的,分别是全局变量,静态全局变量和静态变量
如:
int globalData = 1;
static int staticGlobalData = 1;
int main(int argc, char * argv[]) {
@autoreleasepool {
static int j = 1;
void (^bBlock)() = ^{
j = j+1;
globalData =globalData + 1;
staticGlobalData = staticGlobalData + 1;
NSLog(@"j=%d",j);
NSLog(@"globalData=%d",globalData);
NSLog(@"staticGlobalData=%d",staticGlobalData);
};
bBlock();
}
//输出:
//j=2
//globalData=2
//staticGlobalData=2
对于全局变量与静态全局变量而言由于作用域是全局,所以可以在block中修改其值,但对于静态变量而言,转换后的代码如下:
int globalData = 1;
static int staticGlobalData = 1;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *j;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_j, int flags=0) : j(_j) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *j = __cself->j; // bound by copy注意!
(*j) = (*j)+1;
globalData =globalData + 1;
staticGlobalData = staticGlobalData + 1;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_0b992e_mi_0,(*j));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_0b992e_mi_1,globalData);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_0b992e_mi_2,staticGlobalData);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int j = 1;
void (*bBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &j));
((void (*)(__block_impl *))((__block_impl *)bBlock)->FuncPtr)((__block_impl *)bBlock);
}
return 0;
}
在代码中标注“注意”的地方即可看到区别,当block中访问的自动变量为静态变量时,加入到__main_block_func_0结构体中的为其指针,所以可以通过(*j) = (*j)+1;
的方式来修改静态变量的值,这就是超出变量作用域时使用变量的最简单的方法。
2.__block说明符
对于__block说明符,我们都知道加上了它就可以在block中修改引用的外部变量的值,但是具体是怎么实现的呢?下面附上源码说明
OC源码:
__block int i = 1;
void (^bBlock)() = ^{
NSLog(@"%d",i);
i=3;
NSLog(@"%d",i);
};
i=2;
bBlock();//输出2,3,__block关键字复制的是自动变量的指针
C源码:
struct __Block_byref_i_0 {//标注3
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; //标注1
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {//标注2
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_37e40a_mi_0,(i->__forwarding->i));
(i->__forwarding->i)=3;//标注4
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_37e40a_mi_1,(i->__forwarding->i));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};//标注5
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1};
void (*bBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
(i.__forwarding->i)=2;
((void (*)(__block_impl *))((__block_impl *)bBlock)->FuncPtr)((__block_impl *)bBlock);
}
return 0;
}
从输出结果来看,加上了_ _block说明符后,就好像变量的作用域包含了block内,再来看转换出来的C源码:
标注1处可以看到,对比未加 __block说明符,在__main_block_impl_0结构体重多出了__Block_byref_i_0类型的i变量。
标注2处可以看到在构造函数中,将i的__forwarding指针传递进了__main_block_impl_0结构体,而不是像之前未加__block时传递的仅为简单的值。
标注3处来详细的看__Block_byref_i_0结构体,这就是__block变量变为的结构体实例。其中int i相当于变量i的值也就是被使用的值,__forwarding指针持有指向该实例自身的指针,通过成员变量__forwarding来访问成员变量。所以在之后的main函数中,对i值进行修改时(i.__forwarding->i)=2;
,也是通过__forwarding指针来实现的,也就是说用__block说明符声明变量后,这个变量已经变成了一个block变量,无论是否在block内调用,对于这个变量的操作都得通过它自己约定的__forwarding来访问,也就可实现无论是否在block的函数中,都可正常修改i值。