Skip to content

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;

代码解析

  1. __block 修饰符:

    • __block 修饰符允许在 block 内部修改变量,并且在 block 外部对该变量的修改也会影响 block 内部的值。
    • multiplier 在被 block 捕获时是一个栈上的变量,但由于 __block 修饰,它会被封装为一个对象,当 block 被拷贝到堆上时,这个对象也会随之移动。
  2. block 赋值:

    • 在 viewDidLoad 方法中,定义并赋值了一个 block 给成员变量 blk。这个 block 捕获了 multiplier。当 block 被赋值给一个成员变量或被 copy 时,它会从栈上移动到堆上
  3. 修改 multiplier:

    • 在定义 block 后,multiplier 被修改为 6。
  4. 调用 block:

    • 在 executeBlock 方法中调用了 blk,并传递了参数 4。

forwarding 指针

  • 当 block 捕获 __block 变量时,编译器会创建一个 __Block_byref 结构体并初始化 forwarding 指针指向自身。
  • 如果 block 被复制到堆上,编译器会确保 forwarding 指针指向堆上的副本。

block的循环引用

MAC 下没有问题,ARC 下有泄露

如果不调用,这个环还在