Block¶
面试点¶
blcok 介绍, 截获变量, __block, block 内存管理 , block的循环引用
Block¶
1. 什么是 block¶
block 是将函数及其执行上下文封装起来的对象。
{
int multiplier = 6;
int(^Block)(int) = Mint(int num){
return num * multiplier;
};
Block(2);
}
clang -rewrite-objc file.m
编译命令
他有 isa 指针,函数指针
2. Block 调用¶
就是函数调用
截获变量¶
- 局部变量
- 基本数据类型: 对于基本类型的局部变量是截获其值
- 对象类型: 对于对象类型的局部变量连同所有权修饰符一起截获
- 静态局部变量: 以指针形式截获局部静态变量
- 全局变量
- 静态全局变量 : 不截获全局变量,静态全局变量, 全局变量和静态全局变量在block中可以直接访问,不需要截获,因为它们的生命周期与程序或文件一致。
示例: 局部变量,基本数据类型
{
int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d", Block(2));
}
// result is 12
示例:局部静态变量
{
static int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d", Block(2));
}
// result is 8
示例: 全局变量
int multiplier = 10;
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// static int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d", Block(2));
}
@end
// result is 8 ,似乎也是引用
// 同理全局静态变量 一样
__block¶
一般情况下,对被截获的变量进行赋值操作需要添加__block 修饰符。
记住是赋值,不是使用
示例: 这是使用,不是赋值。
{
NSMutableArray *array = [NSMutableArray new];
void (^Block)(void) = ^{
[array addObject:@1];
};
Block();
NSLog(@"array.first = %@", array.firstObject);
}
示例: 这是赋值,需要__block
{
__block NSMutableArray *array = nil;
void (^Block)(void) = ^{
array = [NSMutableArray new];
};
Block();
NSLog(@"array.first = %@", array.firstObject);
}
对变量进行赋值时,局部变量 无论是基本数据类型还是对象都需要__block 修饰。对于全局变量,静态变量,全局静态变量不需要该修饰符。
__block 修饰的变量变成了对象,有了 isa 指针,同时有个 forwarding 指针
{
__block int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d", Block(2));
}
// result is 8
__block 关键字被用来修饰 multiplier 变量,以便在 Block 内部和外部都能修改该变量的值。__block 关键字使得 Block 内部能够捕获和修改变量的引用,而不是值。
在 ARC 环境下,一个 __block 变量的结构大致如下:
struct __Block_byref_multiplier_0 {
void *__isa;
struct __Block_byref_multiplier_0 *__forwarding;
int __flags;
int __size;
int multiplier;
};
解释:
- __isa:这是一个指向类对象的指针,用于支持 Objective-C 的对象模型。
- __forwarding:这是一个指向自身或另一个 __block 变量对象的指针。它用于确保在不同的作用域中引用相同的 __block 变量时,始终能够正确地访问变量的值。
- __flags 和 __size:这些字段用于管理 __block 变量的内存布局和生命周期。
- multiplier:这是实际存储的变量值。
__block 关键字通过包装变量并使用 __forwarding 指针,确保在 block 内外都能够正确访问和修改变量。这种机制保证了 block 的变量捕获和修改行为。
multiplier = 4; -> (multiplier.__forwarding -> multiplier) = 4;
// 这是栈上创建的block, forwwarding 指向自身。
block 内存管理¶
Block 三种类型
- _NSConcreteGlobalBlock
- _NSConcreteStackBlock
- _NSConcreteMallocBlock
1. _NSConcreteGlobalBlock¶
这是全局 block,它们在全局静态存储区分配,并且其生命周期与应用程序的生命周期相同。通常情况下,不捕获任何外部变量的 block 会被编译器优化为全局 block。
全局 block (_NSConcreteGlobalBlock):这些 block 位于静态数据区,通常由不捕获任何外部变量的 block 组成。它们的 isa 指针指向 _NSConcreteGlobalBlock。
void (^globalBlock)(void) = ^{
NSLog(@"This is a global block");
};
2. _NSConcreteStackBlock¶
这是栈上的 block,它们在栈上分配,随着栈帧的销毁而销毁。默认情况下,捕获外部变量但没有被复制到堆上的 block 是栈上的 block。
栈 block (_NSConcreteStackBlock):这些 block 位于栈上,随着栈帧的销毁而销毁。捕获外部变量但没有被复制到堆上的 block 是这种类型。它们的 isa 指针指向 _NSConcreteStackBlock。
void (^stackBlock)(void) = ^{
NSLog(@"This is a stack block");
};
// 当函数返回后,stackBlock 被销毁。
3. _NSConcreteMallocBlock¶
这是堆上的 block,当栈上的 block 被复制到堆上时(例如,通过 Block_copy 或在需要持久保存 block 的情况下),会变成这种类型。
堆 block (_NSConcreteMallocBlock):这些 block 位于堆上,通过 Block_copy 或其他方式从栈复制到堆上的 block 是这种类型。它们的 isa 指针指向 _NSConcreteMallocBlock。
void (^mallocBlock)(void) = [stackBlock copy];
// 现在 mallocBlock 在堆上,可以在函数返回后继续存在。
4. Block copy 操作¶
MRC下会产生堆存泄露,堆上的没有被释放。
栈上__block 变量的 copy
栈上的 forwarding 指向 堆上的,堆上的 forwarding 指向 自身。
最终都是 (multiplier.__forwarding -> multiplier) = 4
typedef int(^Block) (int num);
@interface ViewController ()
{
Block blk;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
__block int multiplier = 10;
blk = ^int(int num) {
return num * multiplier;
};
multiplier = 6;
NSLog(@"Block class: %@", [blk class]);
//如果输出 __NSStackBlock__,表示 block 在栈上。
// 如果输出 __NSMallocBlock__,表示 block 在堆上。
// 如果输出 __NSGlobalBlock__,表示 block 是全局的。
[self executeBlock];
}
- (void)executeBlock {
int result = blk(4);
NSLog(@"result=%@",@(result));
}
result is 24;
代码解析
-
__block 修饰符:
- __block 修饰符允许在 block 内部修改变量,并且在 block 外部对该变量的修改也会影响 block 内部的值。
- multiplier 在被 block 捕获时是一个栈上的变量,但由于 __block 修饰,它会被封装为一个对象,当 block 被拷贝到堆上时,这个对象也会随之移动。
-
block 赋值:
- 在 viewDidLoad 方法中,定义并赋值了一个 block 给成员变量 blk。这个 block 捕获了 multiplier。当 block 被赋值给一个成员变量或被 copy 时,它会从栈上移动到堆上
-
修改 multiplier:
- 在定义 block 后,multiplier 被修改为 6。
-
调用 block:
- 在 executeBlock 方法中调用了 blk,并传递了参数 4。
forwarding 指针
- 当 block 捕获 __block 变量时,编译器会创建一个 __Block_byref 结构体并初始化 forwarding 指针指向自身。
- 如果 block 被复制到堆上,编译器会确保 forwarding 指针指向堆上的副本。
block的循环引用¶
MAC 下没有问题,ARC 下有泄露
如果不调用,这个环还在