iOS中block的使用详解

block的声明和简单使用

  • 苹果官方文档声明,block是objc对象。
block的定义方式
  • 无返回值无参数的block
void(^block1)() = ^{
    NSLog(@"调用了block1");
};
  • 无返回值有参数的block,参数的类型和传入参数的值不能省略,即int a不能省略
void(^block2)(int) = ^(int a){
       NSLog(@"调用了block2");     
};
  • 有返回值的block,返回值的类型可以省略,要是有参数,参数的类型和传入参数的值不能省略。
int(^block3)() = ^int{
    return 3;
};

block定义总结

总结:block的定义,不管block有没有返回值,block的实现部分都可以省略;若block有参数,则参数类型和传入参数的值不能省略。

  • 小tip:若初学者觉得block格式奇怪,记不住,可以输入inline就可以出现block,如图:
Paste_Image.png

block使用场景之----代理传值

在开发中控制器逆传传值最常用的就是用代理的方法,然而用block也可以实现逆传传值,而且更加简单方便。

  • 场景:假如在窗口的根控制器ViewController中点击屏幕的view,模态弹出一个窗口ModalViewController,再点击ModalViewController的view销毁ModalViewController,同时将想要传的值传给ViewController。

  • 在ViewController中的- touchesBegan方法中,进行到ModalViewController的跳转,同时给ModalViewController的block属性声明,注意!这里并没有调用

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    ModalViewController *modalVc = [[ModalViewController alloc] init];
    modalVc.view.backgroundColor = [UIColor brownColor];
    modalVc.block = ^(NSString *value) {
      
        NSLog(@"%@",value);
    };
    
    // 跳转
    [self presentViewController:modalVc animated:YES completion:nil];
}
  • 在ModalViewController的.h文件中声明一个block属性,这个block属性没有返回值,接收一个字符串类型的参数
@property (nonatomic, strong) void(^block)(NSString *value);
  • 在ModalViewController的.m文件中,点击控制器的view进行block的调用,此时就会将字符串中的值传给ViewController。注意if语句的判断,否则block不存在的时候,会报坏内存访问错误
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (_block) {
        _block(@"我是萌萌哒ModalViewController");
    }
    [self dismissViewControllerAnimated:YES completion:nil];
}
iOS中block的使用详解_第1张图片
Paste_Image.png
block的内存管理
  • block在MRC环境和ARC环境的内存管理不同

MRC

  • 首先,在项目的building settings里面将环境改成MRC的环境
Paste_Image.png
  • block会根据其内部引用的变量的类型不同而在不同的存储空间
  • block可以存在全局区,栈区,堆区(__NSGlobalBlock____NSStackBlock__,__NSMallocBlock__)。
    • 若block中引用外部的局部变量,则打印结果是__NSStackBlock__,说明block存储在栈区
- (void)viewDidLoad {
    [super viewDidLoad];
    //局部变量a
    int a = 3;
    void(^block)() = ^{
        NSLog(@"调用block %d",a);
    };
    NSLog(@"%@",block);
}
Paste_Image.png
  • 若block中的变量的全局变量后者静态变量,则打印结果是__NSGlobalBlock__,说明block存储在全局区
//全局变量a
int a = 3;

- (void)viewDidLoad {
    [super viewDidLoad];
    void(^block)() = ^{
        NSLog(@"调用block %d",a);
    };
    NSLog(@"%@",block);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //静态变量a
    static int a = 3;
    void(^block)() = ^{
        NSLog(@"调用block %d",a);
    };
    NSLog(@"%@",block);
}
Paste_Image.png
  • 那么block什么时候保存在堆区呢?
  • 当block属性被copy修饰的时候放在堆区,打印结果为__NSMallocBlock __
//定义copy修饰的block属性
@property (nonatomic, copy) void(^block)();
- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 3;
    void(^block)() = ^{
        NSLog(@"调用block%d",a);
    };
    self.block = block;
    NSLog(@"%@",self.block);
}
  • 假如我们尝试用retain修饰block,会报这样的警告,同时运行的结果是保存在栈区。
Paste_Image.png
Paste_Image.png
总结
只要block没有引用外部局部变量,block放在全局区
只要Block引用外部局部变量,block放在栈区.
block只能使用copy,不能使用retain,使用retain,block还是在栈区

ARC

  • 若block中的变量的全局变量后者静态变量,则打印结果仍然是__NSGlobalBlock__,block仍然存储在全局区
  • 若block中引用外部的局部变量,则打印结果是__NSMallocBlock__,说明block存储在堆区,和MRC情况不同。
  • 对于ARC下的block属性用strong和copy修饰都是一样的,但是copy的底层会让setter方法多一层对可变不可变属性的判断,浪费资源,所以在ARC的情况下用strong修饰block,同理NSString属性修饰也用strong。
block的内存管理总结
 总结:block里面引用全局变量或者静态变量,放在全局区__NSGlobalBlock__
 
 *  MRC情况:
 *  block里面引用局部变量,存放在栈区__NSStackBlock__
 *  MRC中修饰block只能用copy,不能用retain,因为retain修饰block还在栈中,若block是局部变量,不能包住block的命,再次访问会造成坏内存访问。
 
 *  ARC情况:
 *  block里面引用局部变量,存放在堆区__NSMallocBlock__
 *  block在ARC情况下用strong修饰
 *  对于NSString和block在ARC尽量用strong,因为copy底层的setter方法会对新对象做一次copy操作,还要判断是不是不可变对象,浪费性能
 */
block的循环引用
  • block会对内部的所有的强指针变量都强引用一次,这就会造成block的循环引用,造成内存泄露。下面看例子:

在ViewController中以modal的方式展示ModalViewController,在ModalViewController中调用dismiss方法销毁控制器。则在dealloc方法中会打印ModalViewController被销毁。

然而若定义一个block属性,并在block的实现中做如下操作:

@property (nonatomic, strong) void(^block)();
_block = ^{
    NSLog(@"%@",self);
};

此时当调用ModalViewController的dismiss方法的时候不会调用dealloc方法中的打印语句,说明ModalViewController没有被真正的销毁。因为ModalViewController强引用一个block属性,block会对内部的强指针self进行一次强引用。所以造成循环引用

  • 改进方法是将self改成弱指针,这样就不会造成循环引用
__weak typeof(self) weakSelf = self;
_block = ^{
    NSLog(@"%@",weakSelf);   
};

block的变量传递

  • 请看这样一段代码,大家猜一猜打印的结果是3还是5?
/***************  1  **************/
- (void)viewDidLoad {
    [super viewDidLoad];    
    //普通局部变量a
    int a = 3;
    void(^block)() = ^{
        NSLog(@"%d",a);  
    };
    a = 5;
    block();
}

如果这样呢?

/***************  2  **************/
- (void)viewDidLoad {
    [super viewDidLoad];    
    //静态变量a
    static int a = 3;
    void(^block)() = ^{
        NSLog(@"%d",a);  
    };
    a = 5;
    block();
}

这样呢?

/***************  3  **************/
//全局变量a
int a = 3;
- (void)viewDidLoad {
    [super viewDidLoad];    
    void(^block)() = ^{
        NSLog(@"%d",a);  
    };
    a = 5;
    block();
}

这样呢?

/***************  4  **************/
- (void)viewDidLoad {
    [super viewDidLoad];    
    //用__block修饰的局部变量a
    __block int a = 3;
    void(^block)() = ^{
        NSLog(@"%d",a);  
    };
    a = 5;
    block();
}

实际编译可以得出结果,除了1的打印结果是3,剩下的都是5。那么可以总结出结论如下:

* 如果block中是普通的局部变量,是值传递,即在局部变量声明的那一刻就把局部变量的值放到block中了,之后在修改不会造成影响
* 如果是静态变量,全局变量,__block修饰的变量,block都是指针传递。指针传递的值可以修改。

你可能感兴趣的:(iOS中block的使用详解)