这是我参与「第四届青训营 」笔记创作活动的的第3天
闭包
闭包可以理解为是一个结构体,其主要分为:
- 函数(入口地址)
- 环境(约束变量和自由变量)
Block
- block是闭包在OC中的实现方式
- block可以接受参数也可以有返回值
- block存储在栈、堆中,也可以是全局,在栈上的block可以copy到堆中,具体如下:
| 类型 | 描述 | 环境 |
|---|---|---|
| NSGlobalBlock | 全局Block,保存在数据区(.data段) | 定义在全局区或者没有访问自动局部变量 |
| NSStackBlock | 栈Block,保存在栈区 | 访问了自动局部变量 |
| NSMallocBlock | 堆Block, 保存在堆区 | __NSStackBlock__调用了copy |
- 标准形式:
returnType (^blockName)(parameters) = ^returnType(parameters){
//block内容
};
//注意在block的赋值中,returnType可以省略,编译器会自动检查返回值与声明中的返回值是否一致
变量捕获
Block在初始化的时候会捕获Block中需要的值,这个值不会随着外面的变量的改变而改变,例如:声明一个变量 int value = 1,一个Block捕获了一个值value = 1,当value + 1时,Block中的value值还是1,如果想要Block的值随着外部变量的改变而改变,则需要在声明前加上__block,例如:__block int value = 1
不同点:
- int value = 1,在Block中捕获value的值为值引用
- __block int value = 1,在Block内部对于value的值为引用捕获,也就是说捕获到的是外部的value对象,因此对应的值会同步,内外互相影响
值得注意的是: OC是一门动态语言,所有的方法都是在运行时发送消息objc_msgSend实现,当需要捕获self时,,捕获到的是一个self对象,当需要取其self中的属性时,例如需要取self.name,实际上是向self发送一个消息,告诉他我想要你的name属性,因此,self指针没变,在block向self发送消息时,它能正确地找到变化后的值
循环引用
// ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void (^completionBlock)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.completionBlock = ^{
NSLog(@"%@", self.name);
};
}
@end
在ARC下,系统会在某些场景默认对block执行copy操作,使其变为__NSMallocBlock__,此时block也会有自己的内存引用计数。因此,在代码中,VC强指针指向Block,Block强指针指向VC,形成了闭环,也就是循环引用,导致了内存泄漏,将上述viewDideLoad中的代码改为以下代码即可解决:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.completionBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@", strongSelf.name);
};
}
@end