Object-C高级编程读书笔记(3)——Block的变量截取

之前我们对于Block的定义为 “带有自动变量值的匿名函数”。通过前面的介绍,知道了Block能够保持传入其中的变量的值,即使在Block外部这些传入的值已经结束了其作用域,但是在Block被调用时,仍能够在Block内部获取到这些外部变量的值。

如下面的代码

int main(){
    int val = 10;
    const char *fmt = "val = %d\n";
    void(^blk)(void) = ^{printf(fmt, val);}
    blk();
    return 0;
}

在这里Block的外部变量fmt,与val被传入到Block的执行函数体中,虽然在这里这些变量并未失效,但设想若是在complete类型的Block中,当Block被回调来时,fmt和val可能已经过期,但是在Block中仍保持其值,可以打印出 val = 10;

Block对于变量的截取

这是怎么实现的呢?不废话,同样利用clang将OC代码转换为C++实现
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
printf(fmt, val);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int val = 10;
        const char *fmt = "val = %d\n";
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}

同上一篇中所述类似,Block对象被转换为了__main_block_impl_0结构体类型,其成员也与上一篇中介绍的大致相同,但是却多了两个成员变量const char* fmt, 与int val。再看其构造函数及mian函数中调用构造函数的地方,可知,这两个成员变量正好对应保存了(按值保存)Block外部传进来的变量。
如此一来,只要Block对象不释放,则这两个值会一直跟随着Block对象。再细看Block的函数体
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
printf(fmt, val);}
__main_block_func_0的参数是__cself,也就是Block对象自身的指针,类似于在调用C++类方法时,其函数第一个参数会隐式的默认为this一样。
在函数体内,则用了其自身的成员变量fmt,val作为函数执行时用到的变量。

结论

结合上一篇中揭露的Block对象本质是一个OC类对象,那么Block对象外部传入变量的处理其实是:
Block对象会将外部传入的值按照值传递的方式保存在自身成员变量中
这就难怪当外部变量过期释放后,在Block中仍能够访问到变量的值,因为在Block中始终保留着对外部变量的一个值拷贝。

一个有趣的问题

这里说到,对于外部传进的变量,Block内部都有一份值拷贝。同样的,对于指针类型也是值拷贝。也就是说,对于传入的指针变量,Block内部也会相应的指向同一块内存,而非另辟一块内存来存储相同的数据。那么当外部指针所指向的内存失效后,Block内部的指针,是否还能够在这块内存上获得正确的值呢?
我们这里来做个实验,如下代码
typedef void (^myBlock)(void);
void initBlock(myBlock *blk)
{
    int a = 12;
    int *v = &a;
    printf("In function v=%d &v = %d *v=%d\n",v, &v, *v);
    *blk = ^{
        printf("In block v = %d &v = %d *v = %d\n",v, &v, *v);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        void(^blk)(void) = nil;
        initBlock(&blk);
        blk();
        
    }
    return 0;
}

在函数initBlock中接收了一个Block对象指针,同时用指向函数局部变量的指针V来初始化Block中的指针V(这里按值拷贝,Block中的V与Block外的V指向同一块内存)。当initBlock函数执行完毕,其指向的&a内存失效。我们看下输出结果
In function v=1606416420 &v = 1606416408 *v=12
In block v = 1606416420 &v = 1049040 *v = 1

果然,虽然Block中的v仍在指向1606416420这块地址,但是在函数外面,该地址数据已经失效,输出一个1。同时我们可以注意到,Block中的v与Block外的v在内存中的地址是不一样的,这正也说明了传入Block中的变量,其实是在Block中的一个值拷贝的事实。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值