Block

作者: 曹来东 | 来源:发表于2018-08-31 14:34 被阅读15次

Block本质

  • block本质也是一个OC对象,内部也有一个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
    函数调用: 函数地址,函数通过函数地址调用.
    函数调用环境: 函数参数,返回值.
    image.png
int age = 10;//局部变量
void (^block)(void) = ^{
  NSLog(@"age is %d",age);//打印结果为10
}
age = 20;
block();

Block变量捕获

变量类型 是否捕获到Block内部 访问方式
局部变量 auto YES 值传递
局部变量 static YES 指针传递
全局变量 NO 直接访问

局部变量:在函数内部定义的变量
全局变量:在函数外部定义的变量
是字符串替换.
auto变量:离开作用域会自动销毁
static变量:全局变量 不会销毁.

//全局变量 不会捕获 直接访问
static number = 10;
//局部auto变量,值传递,局部变量会随时销毁,所以捕获到block内部.
int age = 10;//等价代码auto int age = 10;
//局部static变量;地址传递.局部变量会随时销毁,所以捕获到block内部.
static height = 10;
void (^block)(void) = ^{
  NSLog(@"age is %d",age);//打印结果为10,值传递.捕获
 NSLog(@"height is %d", height);//打印结果为20.地址传递,捕获
 NSLog(@"number is %d", number);//打印结果为20,直接访问,不捕获
}
age = 20;
height = 20;
number = 20;
block();

局部变量:栈段
全局变量:数据段
对象:堆
类对象:数据段

实例:

//LDPerson类中的test方法的实现
- (void) test{
//会捕获self对象,因为self是函数参数,函数
//参数是局部变量,局部变量会捕获到block中
 //并且以值传递的方式捕获
void (^block)(void) = ^{
          NSLog(@"%@",self);
}
block();
}
//LDPerson有一个name属性
- (void) test{
void (^block)(void) = ^{
//会捕获self对象,因为self是函数参数,函数
//参数是局部变量,局部变量会捕获到block中
//并且以值传递的方式捕获.捕获self之后,通过self->name来访问name属性
          NSLog(@"%@",_name);
}
block();
}

self是局部变量不是全局变量.

Block类型:

      void (^block)(void) = ^{
            NSLog(@"111");
        };
        NSLog(@"%@",[block class]);
        NSLog(@"%@",[[block class] superclass]);
        NSLog(@"%@",[[[block class] superclass] superclass]);
        NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
//打印结果:
2018-08-30 16:09:50.907192+0800 全局变量[27597:5794698] __NSGlobalBlock__
2018-08-30 16:09:50.907403+0800 全局变量[27597:5794698] __NSGlobalBlock
2018-08-30 16:09:50.907422+0800 全局变量[27597:5794698] NSBlock
2018-08-30 16:09:50.907436+0800 全局变量[27597:5794698] NSObject

可以看到block存在继承结构.是一个OC对象.

  • NSGlobalBlock ( _NSConcreteGlobalBlock )//数据段
  • NSStackBlock ( _NSConcreteStackBlock )//栈
  • NSMallocBlock ( _NSConcreteMallocBlock )//堆

内存分区

应用程序内存分配
代码段
数据段
image.png

Block分类:

block类型 环境
NSGlobalBlock 没有访问auto变量
NSStackBlock 访问了aotu变量
NSMallocBlock NSStackBlock 调用了copy

每一种类型的block调用copy后的结果如下所示

block类型 副本源的存储区 复制效果
NSGlobalBlock 数据段 nothing
NSStackBlock 从栈复制到堆
NSMallocBlock 引用计数增加

ARC模式下编译器对Block自动进行copy操作的情况

  • block作为函数返回值时
  • block有强指针引用时会自动进行copy操作(strong修饰时)
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD API的方法参数时

编译器会自动对block进行copy(堆blockMallocBlock)

block属性建议写法

  • MRC
    @property (copy,nonatomic) void (^block) void;
  • ARC
    @property (copy,nonatomic) void (^block) void;
    @property (strong,nonatomic) void (^block) void;

对象类型的auto变量

当block内部访问了对象类型的auto变量时:

  1. 如果是栈block,不会对auto变量产生强引用.
  2. 如果是堆block,会对Strong引用的auto变量产生强引用.
    如果block被拷贝到堆上,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
    3.如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的auto变量(release)
函数 调用时机
copy函数 栈Block复制到堆时
dispose函数 堆上的Block被废弃时

代码示例:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    LDPerson * person = [[LDPerson alloc] init];
    NSLog(@"1111");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after 三秒后 执行的 代码");
        NSLog(@"%@",person);
    });
    NSLog(@"touchesBegan 执行完成");
}
  • GCD的代码中含有Block,会对该block进行copy操作.所以该Block是堆block
  • 堆block 引用了局部变量Person,且该Person对象被强指针修饰,所以block会对该对象进行强引用.
  • 所以现在的情况是,堆block对person对象是强引用.当block销毁时,person对象才会销毁(release操作).
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    LDPerson * person = [[LDPerson alloc] init];
  __weak  LDPerson  *weakPerson = person;
    NSLog(@"1111");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after 三秒后 执行的 代码");
        NSLog(@"%@", weakPerson);
    });
    NSLog(@"touchesBegan 执行完成");
}
  • 堆block不会堆用weak修饰的person产生强引用,person会立刻释放.

__block内存管理

__Block

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

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

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


    image.png
  • 当block在栈上时,并不会对__block变量产生强引用

  • 当block被copy到堆时
    会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数. _Block_object_assign函数会对__block变量形成强引用(retain)


    image.png
  • 当block从堆中移除时
    会调用block内部的dispose函数.dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的__block变量(release).


    image.png

__block的__forwarding指针

image.png

代码示例:

/**
 ARC下 copy/strong 修饰都可以,
 MRC下 copy
 */
@property (nonatomic,copy) void (^block) (void);
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //block引用.局部变量,会捕获到结构体变量里面.值传递.不会对a引用
    auto int a = 10;
     //block引用.局部变量.且是OC对象类型会捕获到结构体变量里面.地址传递
    //该obj对象为强指针修饰.block对obj强引用,[obj retain]
    NSObject * obj = [[NSObject alloc] init];
    //弱指针局部对象变量.会捕获到结构体变量里面.地址传递
    //block对obj弱引用
    __weak NSObject * weakObj = obj;
    //__block 修饰的局部变量,在block内部才能被修改.
    //__block int b 会将b包装成一个OC对象.
    //__block 修饰的变量,block都会对其产生强引用
    __block int b = 10;
    //强引用block,会进行copy操作,所以该block是堆block
    NSMutableArray * arr = [NSMutableArray array];
    __block NSMutableArray * array = [NSMutableArray array];

    self.block = ^{
        b = 30;
        NSLog(@"%d",a);
        NSLog(@"%@",obj);
        NSLog(@"%@",weakObj);
        //此时没有修改arr指针的值,所以不需要__block修饰
        [arr addObject:@""];
        //此时修改了block外部变量,需要用__block
        //Variable is not assignable (missing __block type specifier)
        array = nil;
    };
    self.block();
}

对象类型的auto变量、__block变量

  • 当block在栈上时,对它们都不会产生强引用

  • 当block拷贝到堆上时,都会通过copy函数来处理它们
    __block变量(假设变量名叫做a)
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 对象类型的auto变量(假设变量名叫做p)
    _Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

  • 当block从堆上移除时,都会通过dispose函数来释放它们
    __block变量(假设变量名叫做a)
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 对象类型的auto变量(假设变量名叫做p)
    _Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

block内部访问变量类型 修饰关键字
对像 BLOCK-FIELD_ISOBJECT
__block 变量 BLOCK_FIELD_IS-BYREF

被__block修饰的对象类型

image.png
此时person为局部变量,代码29行之后person释放.
image.png
此时person为局部变量,block对person强引用.

相关文章

  • iOS开发之Block原理探究

    Block概述 Block本质 Block调用 Block分类 Block循环引用 Block原理探究 Block...

  • block的使用

    定义block 返回类型 (^block名称)(参数) = ^(){block内容}; 调用block block...

  • Block 02 - __block

    Block 02 - __block __block 的作用 __block 可以解决 Block 内部无法修改 ...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS Block

    Block的分类 Block有三种类型:全局Block,堆区Block,栈区Block 全局Block 当Bloc...

  • iOS block 为什么官方文档建议用 copy 修饰

    一、block 的三种类型block 三种类型:全局 block,堆 block、栈 block。全局 block...

  • iOS开发block是用copy修饰还是strong

    Block分为全局Block、堆Block和栈Block1、在定义block没有引用外部变量的时候,block为全...

  • block 初探

    全局block, 栈block, 堆block

  • Block

    一、Block本质 二、 BlocK截获变量 三、__block 修饰变量 四、Block内存管理 五、Block...

网友评论

      本文标题:Block

      本文链接:https://www.haomeiwen.com/subject/eklrwftx.html