Block 之 变量捕获

2018-10-29  本文已影响7人  ychen3022

为了保证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、总结
上一篇 下一篇

猜你喜欢

热点阅读