目录
- 变量捕获
- 变量
---- 变量的基本知识
---- 为什么要捕获变量?
---- 为什么局部变量需要捕获?
---- 为什么全局变量不用捕获?
---- self会被捕获吗?
---- 成员变量会被捕获吗?- 局部变量捕获和数值修改探究
---- 局部自动auto
变量- 静态static变量捕获和数值修改探究
- 全局变量捕获和数值修改探究
- __block修饰符
变量捕获
如果
Block
的执行体使用了外界的局部变量,为了保证Block
内部能够正常访问外部的变量,Block
有一个捕获变量的机制。
相当于往Block
结构体里增加一个成员变量,把值传递给这个成员变量,分为值传递
和指针传递
。
如果执行体使用了外界的全局变量,则不需要捕获,直接使用即可。
变量
-
变量的基本知识
首先变量可以分为两种:局部变量和全局变量。
局部变量分为:局部自动auto变量和局部静态static变量。
全局变量分为:全局变量和全局静态static变量。
-
为什么要捕获变量?
因为变量有作用域的限制,在Block里面使用Block外声明的局部变量,相当于跨函数使用这个局部变量。
如果不存一份到Block里面,是无法使用的,会造成访问无效内存,因为外面的局部变量有可能过了作用域就会自动被销毁。
-
为什么局部变量需要捕获?
因为局部变量只能在方法内部访问,离开作用域(大括号)就会自动销毁。
-
为什么全局变量不用捕获?
因为作用域是全局,无论方法内外都可以随时访问。
-
self会被捕获吗?
会,因为self也是局部变量,我们来回想一下,在OC里调用方法实际上会传递self指针的参数,而且捕获的是指针,所以属于引用传递。
objc_msgSend(id self, SEL _cmd, ...)
所以我们之所以能在每一个方法中使用self,就是因为默认传入self变量。
-
成员变量会被捕获吗?
会,因为访问的成员变量也是局部变量。
![](https://img.haomeiwen.com/i6545546/b447a1119c623d8a.png)
局部变量捕获和数值修改探究
- 局部变量只能在方法内部访问,离开作用域(大括号)就会自动销毁。
- Block内访无法修改局部(自动
auto
)变量。
-
局部自动
auto
变量
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定义一个局部变量
int global = 100;
NSLog(@"global Memory address is :%p ",&global);
// 定义一个Block
void (^aBlock)(void);
// 把Block指向一个代码块
aBlock = ^{
NSLog(@"in block global memory address is :%p ",&global);
NSLog(@"global is :%d",global);
};
// 修改局部变量的值
global = 101;
//调用Block
aBlock();
}
return 0;
}
global Memory address is :0x16fdff2bc
in block global memory address is :0x1011040a0
global is :100
从结果来看到在Block中
不可以直接修改
局部变量,且两个global内存首地址
不同。
我们把OC代码
编译成C/C++
来看下底层实现,仅截取部分如下:
#pragma clang assume_nonnull end
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int global; // Block捕获变量后相当于往Block结构体里增加一个成员变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block的具体代码区执行区
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int global = __cself->global; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_1,&global);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_2,global);
}
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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int global = 100;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_0,&global);
void (*aBlock)(void);
aBlock = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
global) );
global = 101;
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
![](https://img.haomeiwen.com/i6545546/a7ca4c82058feb77.png)
- 我们看到首先在
main函数
中声明并初始化局部变量int global = 100;
。- 初始化
Block
,给__main_block_impl_0
这个结构体传入局部变量global
也就是数值100
,属于值传递
。Block
捕获变量后,往Block
结构体中增加一个局部变量int global
,来接收这个数值100
。- 因为是
值传递
所以在main
函数中int global
的变化不会影响Block
中int global
的值,不存在关联关系。- 具体细节可以看我上面的注释。
静态static变量捕获和数值修改探究
静态变量:用static修饰的变量,特点是在程序运行过程中,一直在内存中存在,且只能在方法内部访问。
Block内可以直接访问和修改
静态变量。
// 定义一个静态变量
static int global = 100;
global Memory address is :0x102c68b20
in block global memory address is :0x102c68b20
global is :101
编译成C/C++
代码进行查看:
#pragma clang assume_nonnull end
// Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *global;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_global, int flags=0) : global(_global) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block的具体代码区执行区
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *global = __cself->global; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_1,&(*global));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_2,(*global));
}
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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int global = 100;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_0,&global);
void (*aBlock)(void);
aBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &global));
global = 101;
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
![](https://img.haomeiwen.com/i6545546/9c4eeb20c1cd96c9.png)
- 我们看到首先在
main函数
中声明并初始化静态局部变量static int global = 100;
。- 初始化
Block
给__main_block_impl_0
这个结构体传入变量global的指针
,属于指针传递
。Block
捕获变量后相当于往Block结
构体里增加一个局部变量int *global
,来接收这个指针
。- 因为是
指针传递
,所以后续以后续在__main_block_func_0
具体执行方法区中首先拿到global
的指针地址。通过地址访问和修改内存上的内容
。- 具体细节可以看我上面的注释。
补充:static变量
一直在内存中存在,所以Block
无论什么时候执行,都可以访问到static
的指针。
全局变量捕获和数值修改探究
全局变量:在程序运行过程中,一只在内存中存在,可以被所有方法访问。
Block内可以直接访问和修改
全局变量。
#import <Foundation/Foundation.h>
// 定义一个全局变量
int global = 100;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"global Memory address is :%p ",&global);
// 定义一个Block
void (^aBlock)(void);
// 把Block指向一个代码块
aBlock = ^{
NSLog(@"in block global memory address is :%p ",&global);
NSLog(@"global is :%d",global);
};
// 修改全局变量的值
global = 101;
//调用Block
aBlock();
}
return 0;
}
global Memory address is :0x102c68b20
in block global memory address is :0x102c68b20
global is :101
编译成C/C++
代码进行查看:
#pragma clang assume_nonnull end
int global = 100;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_1,&global);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_2,global);
}
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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_0,&global);
void (*aBlock)(void);
aBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
global = 101;
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
- Block没有对全局变量进行捕获。
- 全局变量可以被程序内所有方法
直接访问
,也就不用block
捕获。- 剩下的这里不过多赘述了。
__block修饰符
__block
可以用于解决Block内部无法修改auto
变量值的问题。__block
不能修饰全局变量、静态static
变量。
如果我们想在block中修改局部变量的值,那就需要在定义局部变量的时候增加修饰词__block
// 定义一个局部变量
__block int global = 100;
NSLog(@"global内存首地址:%p ",&global);
void(^dBlock)(void) = ^{
NSLog(@"在Block内global内存首地址:%p ",&global);
global = 101;
NSLog(@"在Block内global的值为:%d",global);
};
//调用Block,观察global的值是否会被修改
dBlock();
NSLog(@"在Block外global的值为:%d",global);
global内存首地址:0x101006308
在Block中global内存首地址:0x101064bc8
在Block内global的值为:101
在Block外global的值为:101
结果所示:在定义局部变量的时候增加修饰词
__block
,就可以在Block
中修改局部变量的值。
这是为什么呢?我们接下来进行探究。把OC代码
编译成C/C++
来看下底层实现。部分代码如下:
#pragma clang assume_nonnull end
// __Block修饰之后变成了一个结构体
struct __Block_byref_global_0 {
void *__isa; // isa指针代表该结构体也是一个OC对象
__Block_byref_global_0 *__forwarding; // 指针变量,指向结构体自己的内存首地址。
int __flags;
int __size;
int global; // 我们声明的int global最终存放的位置
};
// Block
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 一个__Block_byref_global_0结构体的对象,可以通过指针变量找到
__Block_byref_global_0 *global; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_global_0 *_global, int flags=0) : global(_global->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block的代码区执行位置
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_global_0 *global = __cself->global; // bound by ref
// global->__forwarding->global
// 通过__forwarding存放的指针地址找到我们初始化的那个__Block_byref_global_0的结构体
// 在找到__Block_byref_global_0结构体下面的global变量
// 最后进行赋值
(global->__forwarding->global) = 101;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_2,(global->__forwarding->global));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->global, (void*)src->global, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->global, 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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// __block int global = 100 <==> 转化成了一个__Block_byref_global_0结构体对象
// 并对该对象进行初始化,
// 其中__forwarding指针指向自己(&global代表了该指针对象的内存首地址)
__attribute__((__blocks__(byref))) __Block_byref_global_0 global = {
(void*)0,
(__Block_byref_global_0 *)&global,
0,
sizeof(__Block_byref_global_0),
100};
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_0,&(global.__forwarding->global));
// 初始化Block
// 给__main_block_impl_0这个结构体传入 &global也就是global对象的指针地址
void(*dBlock)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_global_0 *)&global,
570425344));
((void (*)(__block_impl *))((__block_impl *)dBlock)->FuncPtr)((__block_impl *)dBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_3,(global.__forwarding->global));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
![](https://img.haomeiwen.com/i6545546/7e3b33a0c9114b8f.png)
![](https://img.haomeiwen.com/i6545546/82e23bdd92e17b2d.png)
我们可以发现:
- 通过
__block
修饰后的局部变量global
已经不再是一个局部变量了,而是一个__Block_byref_global_0
的结构体对象。- 系统会把局部变量作为一个成员变量包装进它体内。
- 会把
__forwarding
指针指向自己。
所以不能再把它当成一个局部变量来分析了。
Block
是不可以直接捕获这个__block变量global
的。Block
在初始化的时候会捕获__block变量global
的的指针地址,属于指针传递
。- 在
Block代码块
中我们看到(global->__forwarding->global) = 101;
代表着会通过内存直接访问
的形式找到int global
并修改值。- 具体细节可以看我上面的注释。
-
这里就思考了:
为什么不直接在Block中存储
global
呢,就类似静态static
变量那样,偏偏搞个结构体来存放global
呢?
-
我的想法:
结合Block三种类型的知识我们知道:
- 当了一个
Block
访问了auto变量
,那Block
的类型就是是__NSStackBlock__
类型,存放在栈中。内存由系统控制,如果超过变量作用域就会被系统自动销毁。- 在
ARC
环境下如果block
访问了auto变量
,编译器会根据情况自动执行copy
,变成__NSMallocBlock__
类型,然后将栈上的block
复制到堆上。
当
Block
从__NSStackBlock__
类型转换成__NSMallocBlock__
类型的时候:
Block
会从栈中copy
到堆中,那block
在栈中的变量也会跟着copy
到堆中,让堆Block
持有它。
并且让栈__block变量
的__forwarding
指针指向堆上面的__block变量
。
这样,无论是在Block
语法内外使用__block变量
,还是__block变量
配置在栈上或堆上,都可以顺利地访问同一个__block变量
。
网友评论