关于Block

2019-12-16 17:44:23 浏览数 (1)

1.概述

定义: Block是将函数及其执行上下文封装起来的对象。 特点:

  • 可以嵌套定义,定义Block的方法和定义函数的方法相似;
  • Block可以定义在方法内部或外部;
  • 只有调用Block时,才会执行{}中的代码;
  • 本质是对象,是代码高聚合;

优点:捕获外部变量和降低代码分散程度。 缺点:引起循环引用造成内存泄漏。

使用:

代码语言:javascript复制
声明
typedef int (^myBlock)(int , int);

定义属性
@property (nonatomic,copy) myBlock myOneBlock;

使用
self.myOneBlock = ^int(int a , int b) {
        return a b;
  };

2.Block内存管理

(1).block种类 全局块(NSGlobalBlock):存在于全局内存中, 相当于单例. 栈块(NSStackBlock):存在于栈内存中, 超出其作用域则马上被销毁. 堆块(NSMallocBlock):在于堆内存中, 是带引用计数的对象, 需要自行管理其内存.

(2).判断block储存位置

NSGlobalBlock:不访问外界变量,block就位于全局区,此时对NSGlobalBlock的retain、copy操作都无效。

代码语言:javascript复制
void(^myBlock)(void) = ^{
        NSLog(@"没有访问外部变量,在全局区");
    };
    myBlock();
 NSLog(@"%@",[myBlock class]);

//__NSGlobalBlock__

NSStackBlock: 在ARC环境下,当我们声明并且定义了一个block,当该block访问外界变量时,系统帮我们完成了copy操作:NSStackBlock转变到NSMallocBlock的过程,将栈区的block移到堆区来延长block的生命周期。但使用了__weak或者__unsafe__unretained的修饰符,那么系统就不会为我们做copy的操作,不会将其迁移到堆区。

代码语言:javascript复制
    int a = 10;
    __weak void(^myBlock)(void) = ^{
        NSLog(@"访问外部变量,用__weak修饰,在栈区%d",a);
    };
    myBlock();
    NSLog(@"%@",[myBlock class]);

//__NSStackBlock__

NSMallocBlock: 在ARC环境下,__strong修饰的(默认)block只要捕获了外部变量就会位于堆区。

代码语言:javascript复制
  int a = 10;
     void(^myBlock)(void) = ^{
        NSLog(@"访问外部变量,用默认__strong修饰,在堆区%d",a);
    };
    myBlock();
    NSLog(@"%@",[myBlock class]);

//__NSMallocBlock__

3.block访问外部变量

1.截获局部变量 (1).默认情况

代码语言:javascript复制
  int  c= 10;
    self.MyBlock = ^(int a , int b) {
        return a b c;
    };
    c= 15;
    NSLog(@"%d",self.MyBlock(10,10));
//结果:30

(2).用__block 或static

代码语言:javascript复制
static  int  c= 10; 或
 __block  int  c= 10;
    self.MyBlock = ^(int a , int b) {
        return a b c;
    };
    c= 15;
    NSLog(@"%d",self.MyBlock(10,10));
//结果:35

总结:局部变量放在堆区,默认情况下block只copy变量的数值,所以只能访问不能修改。对于__block ,block是复制引用地址来实现访问,所以即可访问也可修改局部变量的值,用static修饰的静态变量也是以指针形式访问。

代码语言:javascript复制
-------------------------默认----------------
    int  c= 10; //默认
    NSLog(@"block定义前c地址:%p",&c);
    self.MyBlock = ^(int a , int b) {
        NSLog(@"block定义内部c地址:%p",&c);
        return a b c;
    };
    c= 15;
    NSLog(@"block定义后c地址:%p",&c);
    NSLog(@"%d",self.MyBlock(10,10));

    结果:
     block定义前c地址=0x16ef938ac
     block定义后c地址=0x16ef938ac
     block定义内部c地址=0x2834343e0

    总结:
     block定义前:c在栈区
     block定义内部:里面的c是根据外面的c拷贝到堆中的,不是一个c
     block定义后:c在栈区


--------------------用__block 或static---------------------

      __block int  c= 10;
    NSLog(@"block定义前c的地址:%p",&c);
    self.MyBlock = ^(int a , int b) {
        NSLog(@"block定义内部c的地址:%p",&c);
        return a b c;
    };
    c= 15;
    NSLog(@"block定义后c的地址:%p",&c);
    NSLog(@"%d",self.MyBlock(10,10));

   结果:
     block定义前c的地址:0x16ed838a8
     block定义后c的地址:0x282438d58
     block定义内部c的地址:0x282438d58

    总结:
      声明 c 为 __block (__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。)
     block定义前:c在栈中。
      block定义内部: 将外面的c拷贝到堆中,并且使外面的c和里面的c是一个。
      block定义后:外面的c和里面的c是一个。
      block调用前:c的值还未被修改。
      block调用后:c的值在block内部被修改。

2.全局变量 全局变量和静态全局变量都可访问和修改,不被截获。

4.循环引用

对象强引用Block,而Block又持有这个对象,这样就会产生循环引用。打破循环引用的方法:持有对象的属性进行一个弱引用。

解决方法: (1). weak strong 强弱引用(也是最常用的方法)

代码语言:javascript复制
 __weak typeof(self) weakSelf = self;
    self.MyBlock = ^(int a , int b) {
        NSLog(@"%@",weakSelf.str);
    };

(2). __block nil 临时变量

代码语言:javascript复制
  __block TestViewController*weakSelf = self;
    self.MyBlock = ^(int a , int b) {
        NSLog(@"%@",weakSelf.str);
        weakSelf = nil;//一定要把临时变量销毁
    };
  self.MyBlock(10, 10);

(3). block加参

代码语言:javascript复制
   self.MyBlock = ^(TestViewController*weakSelf) {
        NSLog(@"%@",weakSelf.str);
    };
  self.MyBlock(self);

6.面试问题点

(1).什么情况使用__block修饰符? 一般情况下,对被截获变量进行赋值操作需要添加__block修饰符 赋值 != 使用。

使用:

代码语言:javascript复制
NSMutableArray *array = [[NSMutableArray alloc]init];
    void(^myBlock)(void) = ^{
        [array addObject:@"123"];
    };
    myBlock();

总结:block 里只是对数组的操作,不是赋值,不需要用__block修饰.

赋值:

代码语言:javascript复制
__block NSMutableArray *array = nil;
   void(^myBlock)(void) = ^{
       array = [[NSMutableArray alloc]init];
   };
   myBlock();

总结:要用__block修饰,因为数组是赋值操作。

(2).为什么修饰block要用copy? 当我们使用block作为一个对象的属性,需要用copy来修饰block,因为它在栈区,函数执行完会立即释放,block只有经过copy才会从栈区移到堆区,我们就可以自己控制block的生命周期。 Block copy操作之后

(3).gcd的block什么时候销毁?

代码语言:javascript复制
Person *person = [[Person alloc]init];
   person.name = @"hello";
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       NSLog(@"%@",person.name);
   });
Person.m
-(void)dealloc{
   NSLog(@"person被销毁");
}

hello
person被销毁

总结:gcd的block默认会做copy操作,即dispatch_after的block是堆block,block会对Person强引用,block销毁时候Person才会被释放。

代码语言:javascript复制
 Person *person = [[Person alloc]init];
    person.name = @"hello";
    __weak Person*weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",weakPerson.name);
    });
person被销毁
(null)

总结:使用__weak修饰过后的对象,堆block会采用弱引用,所以在函数结束后,Person就会被释放,5秒后gcd就无法捕捉到Person。

代码语言:javascript复制
Person *person = [[Person alloc]init];
    person.name = @"hello";
   
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@",person.name);
         __weak Person*weakPerson = person;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2——————————%@",weakPerson.name);
        });
    });
1-------hello
person被销毁
2——————————(null)

总结:gcd内部只要有强引用Person,Person就会等待执行完再销毁,不会等待后执行的弱引用,会直接释放的。

(4).为什么UIView 的 animation动画块使用了Block,内部使用self不会循环引?

代码语言:javascript复制
 [UIView animateWithDuration:2.0 animations:^{
        NSLog(@"%@",self.str);
    }];

因为UIView 动画块是类方法,不被self持有,所以不会循环引用。

0 人点赞