美文网首页
Block 之 变量捕获

Block 之 变量捕获

作者: ychen3022 | 来源:发表于2018-10-29 00:24 被阅读7次

    为了保证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

    相关文章

      网友评论

          本文标题:Block 之 变量捕获

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