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强引用.

    相关文章

      网友评论

          本文标题:Block

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