首页 > 代码库 > Block存储域探析

Block存储域探析

接《Block截获自动变量实现与__block修饰符内部实现》我们继续探讨Block

留下的问题

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

Block 和 __block变量的实质

名称 实质
Block 栈上Block的结构体实例
__block变量 栈上__block变量的结构体实例

Block类型

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

我们都知道isa指针是指向其class的指针,所以, impl.isa = &_NSConcreteStackBlock; 也就表示这个Block为_NSConcreteStackBlock类型。即栈上的Block。

当然还有

设置对象的存储域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的数据区域(.data区)
_NSConcreteMallocBlock

什么情况下Block是 _NSConcreteGlobalBlock 类型的

根据名称,global(全局)我们创建一个全局的Block,看一下他的编译结果


#import "ViewController.h"
//全局的Block
void (^blk)(void) = ^{NSLog(@"123");};

typedef void(^WxsBlock) ();

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    blk();
}

@end

编译后


struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_fbc035_mi_0);}

static struct __blk_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blk_block_desc_0_DATA = http://www.mamicode.com/{ 0, sizeof(struct __blk_block_impl_0)};

static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);

void (*blk)(void) = ((void (*)())&__global_blk_block_impl_0)

我们发现impl.isa = &_NSConcreteGlobalBlock;

我们再从block作为普通局部变量、函数返回值、函数参数时来看它的类型

有自动变量的截取


@property(nonatomic, assign) blk_t blockAssgin;
@property(nonatomic, strong) blk_t blockStrong;
@property(nonatomic, copy) blk_t blockCopy;


blk_t getBlk_t(int i) {
    blk_t block4 = ^(int count){return count*i;};
    NSLog(@"作为函数返回值,有自动变量截取---%@",block4);
     return block4;
}


{
    //作为普通变量, 有自动变量截取
    blk_t block1 = ^(int count) {
        return count * value;
    };
    NSLog(@"作为普通变量, 有自动变量截取---%@",block1);


    //作为assign类型成员变量 ,有自动变量截取
    self.blockAssgin = ^(int count) {
        return count * value;
    };
    NSLog(@"作为assign类型成员变量 ,有自动变量截取---%@",self.blockAssgin);

    //作为strong类型成员变量 ,有自动变量截取
    self.blockStrong = ^(int count) {
        return count * value;
    };
    NSLog(@"作为strong类型成员变量 ,有自动变量截取---%@",self.blockStrong);


    //作为copy类型成员变量 , 有自动变量截取
    self.blockCopy = ^(int count) {
        return count * value;
    };
    NSLog(@"作为copy类型成员变量 , 有自动变量截取---%@",self.blockCopy);

    //作为函数返回值,有自动变量截取
    getBlk_t(value);

    //作为函数参数,有自动变量截取
    [self actionBlock:^int(int i) {
        return i*value;
    }];
}

- (void)actionBlock:(blk_t)block {
    //作为函数参数,无自动变量截取
    NSLog(@"作为函数参数,有自动变量截取---%@",block);
    block(1);
}

NSLog 日志:

Block[47786:5956467] 作为普通变量,有自动变量截取---<__NSMallocBlock__: 0x60000024c9c0>
Block[47786:5956467] 作为assign类型成员变量,有自动变量截取---<__NSStackBlock__: 0x7fff5fbc39e0>
Block[47786:5956467] 作为strong类型成员变量,有自动变量截取---<__NSMallocBlock__: 0x608000056110>
Block[47786:5956467] 作为copy类型成员变量,有自动变量截取---<__NSMallocBlock__: 0x608000056620>
Block[47786:5956467] 作为函数返回值,有自动变量截取---<__NSMallocBlock__: 0x6080000565c0>
Block[47786:5956467] 作为函数参数,有自动变量截取---<__NSStackBlock__: 0x7fff5fbc3968>

但是通过clang编译出来的结果,他们全部同样全部都是impl.isa = &_NSConcreteStackBlock; 惊不惊喜?意不意外?

无自动变量的截取

{
    //作为返回值,无自动变量截取
    blk_t blockt = getBlk_t();
    NSLog(@"作为返回值,无自动变量截取blockt---%@",blockt);

    //作为普通变量,无自动变量截取,此处尝试了@property(strong,assgin,copy)这三种情况,是相同的结果
    self.blocktest = ^(int count){return count;};
    NSLog(@"作为普通变量,无自动变量截取----%@",self.blocktest);

    //作为函数参数,无自动变量截取
    [self actionBlock:^int(int i) {
        NSLog(@"%d",i);
        return i;
    }];
}

- (void)actionBlock:(blk_t)block {
    //作为函数参数,无自动变量截取
    NSLog(@"作为函数参数,无自动变量截取---%@",block);
    block(1);
}

打印结果
Block[47535:5919554] 作为返回值,无自动变量截取blockt---<__NSGlobalBlock__: 0x1039fd0f0>
Block[47535:5919554] 作为普通变量,无自动变量截取----<__NSGlobalBlock__: 0x1039fd150>
Block[47535:5919554] 作为函数参数,无自动变量截取---<__NSGlobalBlock__: 0x1039fd190>


全部是GlobalBlock

但是通过clang编译出来的结果,他们全部同样全部都是impl.isa = &_NSConcreteStackBlock; 惊不惊喜?意不意外?

通过clang的编译在ARC下只能看到静态情况下(静态代码声明)的Block类型,感觉只能作为参考,毕竟还有运行时。

所以我们可以根据NSLog的打印的结果简单总结一下:

Block类型 有无自动变量截取 使用情况
_NSConcreteGlobalBlock 当作全局变量声明并初始化
全部情况
NSStackBlock assgin类型的成员变量
作为函数参数
_NSConcreteMallocBlock 作为普通的局部变量
作为copy类型的成员变量
作为strong类型的成员变量
作为函数返回值

为什么_NSConcreteGlobalBlock类型的Block要设置在数据区域?

因为在使用全局变量的地方不会截获自动变量。因为它在一开始就已经定义好了。不存在”变量“,所以它的执行在任何时候都是固定的,不会依赖运行时的环境,这种类型的操作全局只有一个就可以,既节省空间又好管理。所以放在全局静态区的数据区域最好不过了。

通过clang编译和NSLog的对比的总结

通过clang编译的结果可知,除了在全局变量声明并创建的时候block被编译成_NSConcreteGlobalBlock其他时候全部都是_NSConcreteMallocBlock类型,我们不难看出,Block的初始状态只有这两种,所以Malloc状态的Block是从Stack状态转变过去的,这个猜想在看《Objective-C高级编程》的时候也和作者不谋而合。

那么我们就来探讨一下它是如何从栈被搞到堆中去的。

根据《Objective-C高级编程》的描述:
它是通过objc_retainBlock()方法来把Block从栈搞到堆中的,
通过objc4运行库的runtime/objc-arr.mm 可知:
objc_retainBlock() = _Block_copy

当一个Block被作为函数返回值时为什么会被搞到堆中,它被做了什么?

blk_t func(int rate) {

    /*
        通过Block语法生成Block,配置在栈上的结构体实例,
        tmp被赋值
    */
    blk_t tmp = &__func_block_impl_0(...);

   /*
    通过_Block_copy将其复制到堆上
    复制后将堆上的地址作为指针复制给变量tmp
   */
    tmp = _Block_copy(tmp);

   /*
    将堆上的Block作为Objective—C对象
    注册到autoreleasepool中,返回该对象
   */
    return objc_autoreleaseReturnValue(tmp);
}

在ARC中Block被复制的操作是编译器自己搞定的,但是编译器也不是万能的

当作为返回值的时候,编译器显然是可以搞定的

但是再作为函数参数的时候就不太好使了,这个时候需要我们手动的去copy Block,这一点通过“作为函数参数,有自动变量截取”验证实例得到了验证,它都是stack类型的

这里肯定会被问,为什么非得copy到堆上??在栈上就不行?

俩字:安全!
复制到堆上的__block变量和Block变量在变量作用域结束时不受影响

什么时候需要手动Copy

向方法或者函数的参数重传递Block时

但是如果在方法或者函数中适当的复制了传递来的参数,那么就不必在调用该方法或者函数前手动复制了,如一下方法:

1,Cocoa框架的方法且方法名字中有usingBlock等时
2,GCD的API

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Block存储域探析