void(^myBlock)(void) = ^() { NSLog(@"定义了一个 Block"); }; [UIView animateWithDuration:0.3 animations:myBlock];
那么为什么需要把 block 当作参数去使用呢?
这就引出了 block 这个时候的使用场景:当自己封装一个类的时候,有些事情由外部决定,但什么时候做由内部决定,(即内部决定执行时间,外部传入具体做些什么)——这个时候就可以使用 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 时使用点语法就能被调用到了呢?(待深究)
#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 属性。

- 全局区和静态区: 它们其实是一样的,从内存上来看,全局变量和静态变量都是保存在静态存储区,生命期和程序一样,都是在静态数据区分配内存。在程序结束后回收内存。
- 堆: 手动管理内存
- 栈: 代码块一过,系统自动回收对应内存区

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); };
- 堆区:使用 copy 进行强引用时 block 会 copy 一份到堆区
在 MRC 环境下用 retain 修辞 Block 会有黄色警告,如下图,警告提示最好使用 copy

int a = 0; void(^block)() = ^{ NSLog(@"调用block"); NSLog(@"%d", a); }; self.block = 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);
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();
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内部访问的是局部变量,那么就是值传递,否则就是指针传递。