Object-C高级编程读书笔记(4)——__block说明符

在上一篇中了解了 在Block中,外部传入的变量是如何被保存在Block对象中的。通过对其实现本质的了解,可以知道对于Block对象中的值拷贝,改变其值,并不能改变Block外部变量的值。事实上,XCode编译器也不允许我们改变传入Block对象中的变量。

但是在变量前加上__block关键字,变量就可以在Block中改变并影响外部的变量值。这显然不是简单的值拷贝能够实现的。那么,其原理又是什么呢?

__block关键字变量

有如下代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int val = 10;
        void(^blk)(void) = ^{ val = 5;};
        blk();
    }
    return 0;
}

很简单,我们用clang -rewrite-objc 命令将其编译为C++代码

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
 (val->__forwarding->val) = 5;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}

是不是眼花了?我们先看main函数

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}

可以看到,val变量与Block对象分别转换为类型

__Block_byref_val_0

__main_block_impl_0
类型。__main_block_impl_0类型我们之前已经分析过,对应Block对象类型,而此时的val变量,竟然也被转换为了结构体

再看__Block_byref_val_0结构体定义:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};


我们先看下__block变量val是如何被初始化的

__block int val = 10;

对应代码

  __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};


可以看到,结构体成员分别被赋值void * 0,指向自己的指针,struct结构体size,val变量值10。

别的还好理解,但这个__forwarding指向自己的指针是干嘛用的呢?别急,我们先看看__block变量是怎么传入到Block中的,而它又是怎么在Block中被赋值的

将__block传入Block中

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        ...
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
...
    }
    return 0;
}

可以看到在Block的结构体定义中,有一个__block变量指针(为什么不直接使用__block变量而是指针呢?这是为了保证在多个Block内,可以通过操作指向同一个__block变量指针的方式实现对同一个__block变量进行操作。)

__Block_byref_val_0 *val;
它就用来存储传入Block的__block变量的。

它在构造函数中被赋值

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, <span style="color:#ff6666;">__Block_byref_val_0 *_val</span>, int flags=0) : <span style="color:#ff0000;">val(_val->__forwarding) </span>

有意思的是,val并没有被直接被赋值为_val,而是被赋值为_val->__forwarding

再看block impl中该变量是如何在Block中赋值的

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
 (<span style="color:#ff0000;">val->__forwarding->val</span>) = 5;}


同样,并没有使用val->val = 5直接赋值,而是间接的使用指向自身的指针__forwarding来设置val的值为5

这给人的感觉就像__block变量在Block中使用时,编译器不会直接使用__block变量指针,而是间接的使用__block->__forwarding指针。这样做的目的是什么呢?且看:

__forwarding存在的理由

在之前我们了解了,Block对象是一种OC对象,其实,Blcok对象按照存储位置的不同,可以分为如下三种

_NSConcreteGlobalBlock  //存在于程序数据区
_NSConcreteStackBlock  //存在在栈上
_NSConcreteMallocBlock //存在于堆上

我们对于数据区与堆上的Block对象,并不存在Block对象释放的危险,在该对象的作用域外也可以安全的访问这些Block对象。

但是对于Stack Block,由于其存在于栈上,超出其作用域后,内存会被回收,其对应的Block对象,当然就不能够再被正确的访问。但是,现实是当存在于栈上的Block超出其作用域后,仍能够被访问。(比如异步执行的Dispatch,其Block在真正被调用时,很可能已经结束了其作用域。)

这是怎么实现的呢?原来,当需要时,栈上的Block会被自动的复制到堆上来存储,这样Block对象的作用域便获得了延长。如书上的插图。



那么对于存在于Block对象中的__block变量呢?

当Block对象被复制到堆上时,其内部变量__block变量也被复制到堆上(注意这里说的是‘复制’不是‘剪切’)。若多个Block对象同时拥有同一__block变量,则当Block被复制到堆上时,__block变量只会在第一次时被复制到堆,其余只会增加堆__block的引用计数。

如图:



前面说到,__block变量会随着Block对象复制到堆上,那么就可能出现__block变量在内存中的两份拷贝:一份在堆上,另一份在栈上。如下代码所示的情况:

__block int val = 0;
void (^blk)(void) = [^{ ++val;} copy]; //copy方法复制Block到堆上,同时__block变量也被复制到堆
++val;  //栈上的val加加
blk();  //堆上的val加加
NSLog(@"%d", val); //输出2

代码分别对栈上的val与堆上的val进行了操作,按说这应当是两个不同的结构体变量,但最终的结果却显示似乎是对同一个val进行了两次++操作?这是怎么实现的呢?

原来是__block变量的_forwarding指针的作用,当__block变量由栈拷贝到堆上时,栈上的_forwarding指针会指向堆上的变量,这样通过操作_forwarding指针,实现了对同一个变量的操作。


总结

1.在Block中可以通过_block变量来改变传入Block中的变量值,_block变量其实是一个_block结构体对象。

2.在适当情况时,Block变量会被拷贝到堆上,使Block变量超出其作用域仍然能够被访问,同时,Block变量中的__block变量也被拷贝到堆上。

3.在栈和堆上的_block变量通过_forwarding指针来保证是对同一个_block变量进行操作。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值