Block截获自动变量实现与__block修饰符内部实现

本文深入探讨Block自动截获变量的机制,分析为何需要截获以及如何通过__block修饰符允许在Block中修改局部变量。通过实例和编译后的CPP代码展示__block变量在结构体中的变化,解释Block如何持有结构体指针以实现值的修改。
摘要由CSDN通过智能技术生成

Block自动截获变量

在Block中访问一个外部的局部变量,Block会持用它的临时状态,外部局部变量的变化不会影响它的的状态。

eg:

typedef void(^WxsBlock) ();


- (void)viewDidLoad {
    [super viewDidLoad];

    int i = 0;
    int j = 2;

    WxsBlock completeBlock = ^(){
        NSLog(@"in block : %d",i);
        NSLog(@"in block : %d",j);
    };

    i++;
    j++;
    NSLog(@"out block : %d",i);
    NSLog(@"out block : %d",j);

    completeBlock();

}

我们通过clang编译成cpp文件来看它做了什么
如果对clang的代码不熟悉,先看Block实现

关键代码:


typedef void(*WxsBlock) ();



struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  int i;
  int j;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _i, int _j, int flags=0) : i(_i), j(_j) {
    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) {
  int i = __cself->i; // bound by copy
  int j = __cself->j; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_5c4327_mi_0,i);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_5c4327_mi_1,j);
    }

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"));

    int i = 0;
    int j = 2;

    WxsBlock completeBlock = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, i, j));

    i++;
    j++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_5c4327_mi_2,i);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_5c4327_mi_3,j);

    ((void (*)(__block_impl *))((__block_impl *)completeBlock)->FuncPtr)((__block_impl *)completeBlock);


}

对比Block实现 中的解析来看,我们发现
__ViewController__viewDidLoad_block_impl_0 结构体种多了

    int i;
    int j;

两个变量,并且在其构造方法中多了(int _i, int _j,)的传参和: i(_i), j(_j)的赋值。

在继续深入,我们看到i 和 j__ViewController__viewDidLoad_block_func_0结构题中通过__ViewController__viewDidLoad_block_impl_0的对象指针创造了一个新的局部变量,也叫i``j

看到这里,我们应该明白了:
1,在block创建的时候,如果block中使用了局部变量,block会在爱其__ViewController__viewDidLoad_block_impl_0结构体中复制一个同样类型,同样值的变量。
2,block的执行动作是一个方法指针__ViewController__viewDidLoad_block_func_0,在执行这个方法时,block讲表示自身的__ViewController__viewDidLoad_block_impl_0 *__cself当作参数传递进去,间接的获得了其中的i j的值,并使用。

所以block的接获自动变量这样看起来也并没有什么,就是简单的复制值而已。

但是我们可能会产生一个疑问,为什么要截获?

这个就得来考虑应用场景了

scene:

一个WxsTool工具类,有一个检测数据的功能,检测完毕后通过自身持有的block返回结果,如图:

如果block在创建时没有捕获变量model,而是获取它的指针。
在执行检测处理的时候,model在其他的地方改变了,那么检测过程会调用block通知结果,这时获取的结果很有可能和预想的不一样,而你也不好找到原因。

总的来说,block的使用和创建是分开的,在其之间的操作并不能保证对局部变量值的变动性有掌控。所以,干脆直接补货,在创建那一个时刻是什么,就是什么,这样就避免了在使用过程冲被更改的问题。

有点数据同步的味道在里边。

好了 ,问题总是一个接一个,又会有人问,我就是想让这个model在处理时也可以被改变怎么办?

其实这个问题还和另外一个问题关联:

在block中为什么不能更改局部变量的值?

__block 我们走!!!

__block 修饰符终于登场了

我们写一个OC 使用__block修饰符的代码:

typedef void(^WxsBlock) ();

- (void)viewDidLoad {
    [super viewDidLoad];

    __block int i = 0;

    WxsBlock completeBlock = ^(){
        NSLog(@"before change:%d",i);
        i = 6;
        NSLog(@"after change:%d",i);
    };

    completeBlock();

}

clang编译出来的代码:


struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    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) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

NSLog((NSString*)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_c50572_mi_0,(i->__forwarding->i));

        (i->__forwarding->i) = 6;
        NSLog((NSString*)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_c50572_mi_1,(i->__forwarding->i));
}

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->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {
    _Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}

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
};

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"));

    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

    WxsBlock completeBlock = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)completeBlock)->FuncPtr)((__block_impl *)completeBlock);

}

我们发现,相较于不添加__block修饰符,多出了一个

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

结构体,它的作用是把被__block修饰符修饰的变量转换成struct结构体

我们进一步看它的应用。

1,在__ViewController__viewDidLoad_block_impl_0结构体中多了__Block_byref_i_0 *i而不是之前的int i;,其中的__forwarding指针是一个指向自身的指针。这个指针的作用牵扯到了block的作用域,我们这次不说,敬请听下回分解

2,在__ViewController__viewDidLoad_block_impl_0结构体中的构造方法中传入了__Block_byref_i_0 *i并且有一个i(_i->__forwarding)的操作,相比较值钱的i(_i)可知,这里将_i->__forwarding赋值给了impl_0结构体中的i.其实还是指向自己(i)。

__ViewController__viewDidLoad_block_desc_0结构体中也发生了很大的变化

原来的desc_0结构体只有一个表示大小的size 一个冗余的reserved
现在多了

  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);

两个函数

这两个函数我们暂且解释为一个copy函数,一个释放函数,暂且和 MRC中retain release的作用,我们后边在说Block存储域的时候这两个函数的作用会不言而喻的。

综述

我们最应该关注的点在于,被__block修饰的变量转换成了

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

结构体,

在block 的struct中 持有了此结构体的指针,而不是变量的值,这样就可以通过它来访问内存,所以,可以对值进行修改。

知道这个的同事我们又遇到了几个问题:

1,__Block_byref_i_0 *__forwarding;这个指向自身的指针是什么鬼,有什么作用,什么时候用?
2,Desc_0结构体中多出来的void (*copy) void (*dispose)这两个方法有什么作用,什么时候用?

这两个问题下回分解。有问题欢迎来怼,怼怼更健康

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值