为了保证block内部能够正常访问外部的变量,block有个变量捕获机制,即捕获外部变量。
前言: 搞清成员变量、局部变量、全局变量
屏幕快照 2018-10-29 上午12.20.32.png此处再转载一篇文,介绍auto,static,register,extern修饰变量的区别:https://www.cnblogs.com/Lyush/archive/2013/01/09/2852625.html
下次有空再自己整理(小心心标记住,回头一定要整理)
1、对局部变量捕获
<1>auto变量(自动变量)
//auto变量(自动变量) : 离开作用域会自动销毁
//注意:一般局部变量前面有个auto的标示来修饰,只不过auto常被省略不写
int age = 10;// auto int age = 10;
void(^myBlock1)(void) = ^{
NSLog(@"age is %d",age);
};
age = 20;
myBlock1();
=================================================
打印结果:[14995:3091224] age is 10
为什么打印结果是10而不是20呢?
带着疑问,我们通过通过clang 命令来观察ViewController.m的.cpp文件:
//此处struct为myBlock1的底层结构
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
//已经将age这个值传进来了,block内部的age这个值就保存为10了
int age;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以得出结论:当定义myBlock1的时候,已经捕获了age为10的这样一个变量,当将age重新赋值为20的时候,myBlock1的结构没有发生改变,所以打印结果为10。
<2>static变量(静态变量)
//static变量(静态变量): 全局只存在一份,一直存储在内存中
static int height = 160;
void(^myBlock2)(void) = ^{
NSLog(@"height is %d",height);
};
height = 165;
myBlock2();
=================================================
打印结果:[14995:3091224] height is 165
这次为什么打印结果是165而不是160呢?
同样,我们通过通过clang 命令来观察ViewController.m的.cpp文件:
//此处struct为myBlock2的底层结构
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
//注意:传递的是height的地址(指针传递),将height这个值的存放地址传进来了,block访问的时候是去该地址取值的
int *height;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int *_height, int flags=0) : height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以得出结论:当定义myBlock2的时候,也捕获了height变量,只不过变量被static修饰,所以捕获的是height这个变量的存储地址,当myBlock2通过地址去取值的时候,自然取到的是height的当前值,所以打印结果为165。
2、对全局变量不捕获
上面讲的是两种类型的局部变量,下面看全局变量
//age、height为全局变量
int age = 10;
static int height = 160;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
void(^myBlock3)(void) = ^{
NSLog(@"age is %d",age);
NSLog(@"height is %d",height);
};
age = 20;
height = 165;
myBlock3();
}
=================================================
打印结果:BlockTest[2222:225124] age is 20
BlockTest[2222:225124] height is 165
通过通过clang 命令来观察ViewController.m的.cpp文件:
int age = 10;
static int height = 160;
// @implementation ViewController
//此处struct为myBlock3的底层结构
//可以看出并没有捕获age、height这个变量
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
//此处直接访问全局变量age和height
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cp_f1q8npcs2b91f_9szlk2t6f00000gn_T_ViewController_a91b1c_mi_0,age);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cp_f1q8npcs2b91f_9szlk2t6f00000gn_T_ViewController_a91b1c_mi_1,height);
}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
void(*myBlock3)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
age = 20;
height = 165;
((void (*)(__block_impl *))((__block_impl *)myBlock3)->FuncPtr)((__block_impl *)myBlock3);
}
可以得出结论:对于全局变量,block不会捕获。而是直接访问全局变量。
3、拓展问题
对于下面的这样的写法,myBlock会捕获变量age吗?
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic,assign)int age;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
void(^myBlock)(void) = ^{
NSLog(@"_____%@",self.age);
};
myBlock();
}
@end
让我们看看他的编译源码:(认真看注释哟)
//此处struct为myBlock的底层结构
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
//这里面捕获的并不是单纯的一个属性,而是ViewController这个类
ViewController *self;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
ViewController *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cp_f1q8npcs2b91f_9szlk2t6f00000gn_T_ViewController_94764c_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
//这个就是我们ViewController里面的viewDidLoad方法
//一般OC函数的底层转成的C语言函数,他默认接受两个参数: ViewController * self 和 SEL _cmd
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
//可以看出 在定义myBlock的时候,会将self传入__ViewController__viewDidLoad_block_impl_0里面
//然后在myBlock的底层结构__ViewController__viewDidLoad_block_impl_0里面,self就是这个传入的self了
void(*myBlock)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
static int _I_ViewController_age(ViewController * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_ViewController$_age)); }
static void _I_ViewController_setAge_(ViewController * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_ViewController$_age)) = age; }
所以,对于这种block 里面调用某个类、或者某个类的成员变量的时候,block都是会捕获这个类的。
因为这种情况下,在底层都是会将self指代的这个类作为参数传入block 的构造函数,参数作为一个局部变量,所以block当然会捕获变量的啦。
4、总结
-
<1>block捕获哪些变量?哪些变量不捕获?
局部变量捕获 :其中auto局部变量捕获它的值,static局部变量捕获他的地址
全局变量不捕获
屏幕快照 2018-10-28 下午11.44.09.png
网友评论