小码哥底层原理笔记:Block变量捕获

前面我们看到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();
}

最后总结如下:


block变量捕获总结
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容