前面我们看到Block是会将捕获到的变量保存在__main_block_impl_0
结构体中,那么是不是所有变量都会被捕获呢?肯定不是的。接下来将变量分为两类去讨论。
局部变量
在局部变量中又有默认的auto变量和Static变量。
我们看下面这段代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;//auto变量,auto关键字通常省略
static int height = 10;//Static变量
void (^block)(void) = ^(){
NSLog(@"age is %d, height is %d",age, height);
};
age = 20;
height = 20;
block();
}
return 0;
}
//执行block打印: age is 10, height is 20
我们将其翻译成.cpp源码可以看到Block捕获到了这两个变量:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;//捕获的是age值
int *height;//捕获的是height的内存地址
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
static int height = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));//age直接把值传进去,height是取它的地址传过去
age = 20;
height = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
我们通过代码可以看到虽然age和height都捕获到了block里面,但是当我们执行block()的时候打印出来的age依旧是10,而height是20。那是因为我们auto局部变量捕获的是它的值,从main函数可以看出来定义block的时候是直接把age值传进去的。
而height不一样,因为height是static修饰的,它是一直保存在内存中的,所以block捕获的时候直接捕获的是它的内存地址,因为它只要一直存在内存中,那么它的内存地址是不会变的,从main函数也可以看到定义block的时候是直接把height的内存地址传进去的。所以当我们改变了height的值时,我们执行block后依旧能打印出最新的值。
以上就是局部变量的捕获原理。
那么为什么我们要捕获局部变量呢?
因为我们执行block的时候实际上是调用了这个函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_y0_thwqbdb11zq5wklyb8jyy4km0000gn_T_main_c44a07_mi_0,age, (*height));
}
而我们的局部变量时定义在main函数中的,我们从一个函数中要去访问另一个函数的局部变量,这种跨函数访问,如果我们定义block的时候不把变量捕获到的话,那执行block的时候就无法访问到局部变量了,因为局部变量作用域只在本函数内。
全局变量
全局变量同样也分为默认的auto全局变量和static全局变量
我们看以下代码
int age_ = 10;//默认auto全局变量
static int height_ = 10;//static全局变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^(){
NSLog(@"age is %d, height is %d",age_, height_);
};
age_ = 20;
height_ = 20;
block();//打印,age is 20,height is 20
}
return 0;
}
我们同样将其翻译成.cpp源码,如下:
int age_ = 10;
static int height_ = 10;
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;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
age_ = 20;
height_ = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
我们看到__main_block_impl_0
里面并没有捕获到的变量,全局变量依旧是全局变量放在外面,是一直都在内存中的。所以我们不需要捕获进来,因为一直都在内存中,block需要访问的时候直接访问就是了。当我们执行block的时候取的就是age和height的最新值。
以上就是block变量捕获的原理。
这里还有一种情况,是我们经常遇到的。我们新建一个Person类
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)test;
@end
#import "Person.h"
@implementation Person
//所有OC的方法翻译成C语言函数,前面两个参数都是(Person * self, SEL _cmd)
- (void)test{
void (^block)(void) = ^{
NSLog(@"----------%p",self);
};
block();
}
//如果test函数要访问name属性,那么肯定是先捕获到self,然后访问self->_name
//- (void)test{
// void (^block)(void) = ^{
// NSLog(@"----------%p",_name);
// };
// block();
//}
@end
我们在Person类里面定义了一个test函数,里面定义一个block,这个block访问self。这个self是局部变量还是全局变量呢?我们看一下.cpp源码就知道了
self被捕获到block里面
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
test函数最终是转换成这样
static void _I_Person_test(Person * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));//把self传进去了
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
我们看到self是做为参数传进去的,应该是作为局部变量来看。
如果我们这个test函数访问的是name属性呢?像这样
- (void)test{
void (^block)(void) = ^{
NSLog(@"----------%p",_name);
};
block();
}
这样其实是先捕获到self,然后再访问self里面的name属性的,相当于这样
- (void)test{
void (^block)(void) = ^{
NSLog(@"----------%p",self->name);
};
block();
}
最后总结如下: