Block使用的简单总结

【Block的简单使用】
  1. block 当作参数来传递
  如下定义一个没有返回值无参数的 block ,并把它作为参数,让系统调用,注意:这里是系统在调用,因为 UIView 动画结束是系统调用的。
    void(^myBlock)(void) = ^() {
        NSLog(@"定义了一个 Block");
    };
    [UIView animateWithDuration:0.3 animations:myBlock];

  那么为什么需要把 block 当作参数去使用呢?

  这就引出了 block 这个时候的使用场景:当自己封装一个类的时候,有些事情由外部决定,但什么时候做由内部决定,(即内部决定执行时间,外部传入具体做些什么)——这个时候就可以使用 block 来作为参数。

  2. block 当作返回值来使用
  如下在控制器写下面代码, test 为方法名, void(^)(void) 就是 block 的类型,它就是方法的返回值类型。
- (void(^)(void))test {
    return ^{
        NSLog(@"test");
    };
}

  那么上面这个方法该如何调用呢?

  第一种常规方式: [self test](); ,因为 test 是一个方法,所以 [self test] 调用方法后得到的是一个 block,而执行 block 只要在它后面加  () 就可以。所以 [self test](); 打印出了 test。

  第二种方式: self.test(); 这样类似于属性的 get 方法,后面加一个  () 就相当于执行 block。这里可以留存一个疑问:oc 的 get 方法其实本质是 Xcode 的编译器特性,使用点语法可以对应到属性的 get 方法。那为什么方法返回值为 block 时使用点语法就能被调用到了呢?(待深究)

  block 链式编程
  把方法调用通过语法链接起来,可读性非常好。如下代码,AddTool 继承自 NSObject,用它来执行链式累加。
  .h 文件
#import <Foundation/Foundation.h>

@interface AddTool : NSObject

- (AddTool *(^)(NSInteger))add;

@property (nonatomic, assign) NSInteger result;

@end

  .m 文件

#import "AddTool.h"

@implementation AddTool

- (AddTool *(^)(NSInteger))add
{
    return ^(NSInteger value) {
        _result += value;
        return self;
    };
}

@end

  链式调用如下:

    AddTool *addTool = [AddTool new];
    NSInteger result = addTool.add(5).add(3).add(1).result; // NSLog: 9

【Block 逆传值】

  这里用控制器跳转举例 block 的逆传值。A push 到 B 之后,B 传值到 A 中。可以在 B 头文件声明一个 block 属性,在 .m 文件中执行这个 block。

  B  .h 文件:

@interface BViewController : UIViewController

@property (nonatomic, strong) void(^callBack)(NSString *str);

@end

  B .m 文件

- (void)viewDidLoad {
    [super viewDidLoad];
    self.callBack(@"123");
}

  在 A 中 push 处的代码就可以收到 B 传过来的值。

    BViewController *bVC = [[BViewController alloc] init];
    bVC.callBack = ^(NSString *str) {
        NSLog(@"%@", str); // NSLog: 123
    };
    [self.navigationController pushViewController:bVC animated:YES];

【Block的循环引用】

  循环引用的描述:Block的拥有者在Block作用域内部又引用了自己,因此导致了Block的拥有者永远无法释放内存,就出现了循环引用的内存泄漏。

  发生的场合:ARC 中 block 为 strong 或 copy 属性,在Block内部使用了当前类的self属性,同时这个类包含了别一个类的 Block 属性。

举例:还是上面的传值的例子,现假如在A的.m文件的block里使用了self.view.backgroundColor = [UIColor redColor] 现在这些对象之间的引用关系如下图所示:
因此这样就就造成了循环引用。
 
Block 内循环引用的解决:
在如下的例子,可以在 Block 外加上这样一句(如果将 typeof 用于表达式,则该表达式不会执行。只会得到该表达式的类型。)
 __weak typeof(self) weakSelf = self; 或  __weak AViewController *weakSelf = self; 
 
【Block 内存分析】
  1. 内存5大区 —— 堆, 栈, 方法区,静态区(全局区),常量区
  • 全局区和静态区: 它们其实是一样的,从内存上来看,全局变量和静态变量都是保存在静态存储区,生命期和程序一样,都是在静态数据区分配内存。在程序结束后回收内存。
   它们之间作用域有所不同,全局变量的作用域是整个项目,静态全局变量是当前程序文件,静态局部变量是当前函数体内。
  • : 手动管理内存    
  • 栈: 代码块一过,系统自动回收对应内存区
 首先来了解一下 Block,苹果官方文档 Block 是对象。(因为它是对象,所以用%@格式打印可以看到它的内存分配区),文档描述第一句话如下:
 

  2. MRC 下 Blcok 内存分配

    在 ARC 之前,Block 的内存管理需要区分是 Global (全局)、Stack (栈) 还是 Heap (堆)。

  • 全局区: 如下定义一个Block,并将它使用, 它的打印结果为 <__NSGobalBlock__: 0x1075c076> ,global 表示全局区。全局区表示到处都可以使用。(ps:假如block内部访问static修辞的外部局变量,那么它也是在全局区,关于静态区与全局刚刚上面已经解释过)  
    void(^block)() = ^{
        
    };
    NSLog(@"%@", block);
  • 栈区:如下当Block内访问一个外面的局部变量a,它的打印结果为 <__NSStockBlock__: 0x7fff5baa09d8> ,stack表示栈区
    int a = 0;
    // block 存放在全局区
    void(^block)() = ^{
        NSLog(@"%d", a);
    };
总结如下:Block的内存分配与它内部访问的变量有关,如果访问的是全局变量,那Block会在全局区;  如果访问局部变量,那 Block 会分配到栈区。(什么也不访问默认在全局区)
  • 堆区:使用 copy 进行强引用时 block 会 copy 一份到堆区

  在 MRC 环境下用 retain 修辞 Block 会有黄色警告,如下图,警告提示最好使用 copy

 
假如现在就用 retain,并且在 block 内访问了一个局部变量 a (这个 a 就写在 Block 的上面),代码如下,这时发现一运行程序就漰了。(坏内存访问,报错会出现在使用 self.Block 的地方)
    int a = 0;
    void(^block)() = ^{
        NSLog(@"调用block");
        NSLog(@"%d", a);
    };
    
    self.block = block; // 报错
这一点总结:在非ARC下不能用 retain 引用 block,因为这样不会把 block 放在堆里,它会在栈区,所以代码区一出括号就会销毁,于是会报错。只要使用 copy 就可以把 block 放到堆里面。这样block就不会销毁。——所以说 block 要用 copy 都是老程序员说的,因为那时没有ARC.
 
  3. ARC 下 Block 内存分配
  ARC之后,苹果自动会将所有原本应该放在栈中的Block全部放到堆中。

  首先,全局的 Block 比较简单,一句话就可以讲完:凡是没有引用到 Block 作用域外面的参数的 Block 都会放到全局内存块中,在全局内存块的 Block 不用考虑内存管理问题。(放在全局内存块是为了在之后再次调用该 Block 时能快速反应,当然没有调用外部参数的 Block 根本不会出现内存管理问题)。

   所以 Block 的内存管理出现问题的,绝大部分都是在堆内存中的 Block 出现了问题。实际上属于 Block 特有的内存管理问题就只有一个:循环引用。

  • 全局区:还是如下代码,默认 Block 还是放在全局区,没访问外部变量就都是在全局区,与是否ARC无关
    void(^block)() = ^{
        
    };
    NSLog(@"%@", block);
  • 堆区: 访问一个外部局部变量或对象时,代码如下,它的打印结果 <__NSMallocBlock__: 0x7f9f5baa09d8> ,这个 malloc 分配的内存是表示在堆上。(这有点像ARC下默认一个对象就是强引用,好处是不会一创建就销毁。)
    int a = 0;
    void(^block)() = ^{
        NSLog(@"%d", a);
    };
    NSLog(@"%@", block);
 【Block 内变量值传递与指针传递】
  看下面代码,这个很简单,block 打印出来肯定是 10
    int a = 5;
    a = 10;
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 10
    };
    block();

  1. 值传递情况

  再看如下代码,这个打印出来是5,虽然 block() 执行之前 a = 10,但因为 a = 5 之后就立即被传递进了 block,只是还没有执行而已,即内存已配好。而且这时的 block 是值传递,这时外面更改无法改变里面的值。(如果是指针传递则外面可以更改里面的值)

    int a = 5;
    // 值传递
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 5
    };
    a = 10;
    block();
  
  2. 指针传递情况 
  那么再看如下代码,这个打印结果会是10。原因:只要局部变量生命周期是整个 app 运行都在,那么就是指针传递这里加 static 后,生命周期就与整个程序同存亡了。与全局区一样(这一点上面 Block 内存分析时分析过了)
    static int a = 5;
    // 指针传递
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 10
    };
    a = 10;
    block();

  下在情况也是指针传递,所以打印出来也会是10

    __block int a = 5;
    // 指针传递
    void(^block)() = ^{
        NSLog(@"%d", a); // NSLog: 10
    };
    a = 10;
    block();

  block变量传递总结:如果在block内部访问的是局部变量,那么就是值传递,否则就是指针传递。

原文地址:https://www.cnblogs.com/buerjj/p/5497706.html