美文网首页iOS底层原理
小码哥底层原理笔记:Block变量捕获

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

作者: chilim | 来源:发表于2020-07-09 14:17 被阅读0次

    前面我们看到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变量捕获总结

    相关文章

      网友评论

        本文标题:小码哥底层原理笔记:Block变量捕获

        本文链接:https://www.haomeiwen.com/subject/zbsbzhtx.html