__block修饰符(四)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了__block修饰符(四)相关的知识,希望对你有一定的参考价值。

参考技术A

1、基本数据类型的auto局部变量

在block中无法修改基本数据类型的auto局部变量,修改时会报Variable is not assignable (missing __block type specifier)错误。block对基本数据类型的auto局部变量是值捕获,无法去修改外部的变量。

2、static修饰的基本数据类型局部变量

3、全局变量
在block中可以修改全局变量,因为全局变量在任何作用域都可以调用,block不会对其进行捕获。

4、对象局部变量

block对对象局部变量是指针捕获,会对其强引用,在内部可以修改变量而不报错

1、__block可以用于解决block内部无法修改auto变量值的问题

2、__block不能修饰全局变量、静态变量(static)

3、编译器会将__block变量包装成一个对象

2-3加了__block

2-4没加__block
从2-3中可以看到,加了__block后,内部age是一个__Block_byref_age_0对象

__Block_byref_age_0中包含了isa指针,所以变量为对象。
__forwarding是指向自己的指针
age是变量的值
block通过__forwarding指针去修改age的值

这里个Block里输出的age为20,因为加了__block修饰符后,编译器会将__block变量包装成一个对象,block对其是指针捕获,可以通知指针去修改值。

不加__block时,block是直接强引用的Person对象,但是现在是对__Block_byref_person_0进行了强引用。

__Block_byref_person_0内部才是对Person对象强引用,比没有__block多了__Block_byref_person_0这一步。__Block_byref_person_0内部还有一个__Block_byref_id_object_copy和__Block_byref_id_object_dispose,其作用跟block内部的copy和dispose一样,可以滑到上面看block的copy和dispose的讲解。

Block变量捕获详解(一)
Block的三种类型(二)
Block对象变量捕获(三)
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 = { 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当作全局变量声明并初始化
全部情况
NSStackBlockassgin类型的成员变量
作为函数参数
_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

以上是关于__block修饰符(四)的主要内容,如果未能解决你的问题,请参考以下文章

block的基本理解

5_JavaSE_修饰符

阶段1 语言基础+高级_1-3-Java语言高级_02-继承与多态_第6节 权限修饰符_6_四种权限修饰符

Java_修饰符详解

2016 2 - 23 arc中的所有权修饰符(_strong修饰符与_weak修饰符)

内存管理的思考方式2(ARC下)