美文网首页
iOS中,如何判断一个 Block 是全局块、栈块还是堆块?

iOS中,如何判断一个 Block 是全局块、栈块还是堆块?

作者: 博文得礼 | 来源:发表于2025-05-07 10:40 被阅读0次

在 iOS 中,Block 有三种基本类型,分别对应不同的存储区域:

全局块(NSGlobalBlock)、

栈块(NSStackBlock)、

堆块(NSMallocBlock)。

区分它们的核心是通过 存储位置、是否捕获外部变量 以及 是否执行过 copy 操作。以下是具体区分方法和细节:

一、Block 类型的本质与分类

1. 全局块(NSGlobalBlock)

• 存储位置:全局静态存储区(程序数据段)。

• 特点:

不捕获任何外部变量(包括全局变量、静态变量)。

生命周期与程序一致,无需内存管理(分配/释放)。

在 ARC 和 MRC 下表现一致,不会被 copy 操作影响(因为本身就在全局区)。

• 示例:

void (^globalBlock)(void) = ^{

    NSLog(@"Global Block"); // 未捕获任何局部变量

};

NSLog(@"%@", [globalBlock class]); // 输出:__NSGlobalBlock__(或 NSGlobalBlock)

2. 栈块(NSStackBlock)

• 存储位置:栈区(函数栈帧内)。

• 特点:

捕获了外部自动变量(局部变量,包括 self、成员变量等)。

生命周期受限于所在作用域,超出作用域后会被系统自动释放。

在 MRC 下,栈块不会自动 copy 到堆区,需手动调用 copy 方法;在 ARC 下,某些场景会自动触发 copy(见下文)。

• 示例:

int age = 18;

void (^stackBlock)(void) = ^{

    NSLog(@"Age: %d", age); // 捕获了局部变量 age

};

NSLog(@"%@", [stackBlock class]); // 输出:__NSStackBlock__(或 NSStackBlock,ARC 下可能已自动 copy 为堆块,需看上下文)

3. 堆块(NSMallocBlock)

• 存储位置:堆区。

• 特点:

由栈块通过 copy 操作产生(包括 ARC 下的自动 copy)。

生命周期由内存管理机制控制(MRC 下需手动 release,ARC 下自动管理)。

是最常用的 Block 类型(如作为属性、参数传递时)。

• 示例:

int age = 18;

void (^heapBlock)(void) = [^{ // MRC 下显式 copy,ARC 下某些场景隐式 copy

    NSLog(@"Age: %d", age);

} copy];

NSLog(@"%@", [heapBlock class]); // 输出:__NSMallocBlock__(或 NSMallocBlock)

二、区分 Block 类型的方法

1. 通过 class 方法打印类型

直接调用 [block class] 输出类名:

• 全局块:__NSGlobalBlock__(或 NSGlobalBlock,iOS 版本可能影响前缀)。

• 栈块:__NSStackBlock__(或 NSStackBlock)。

• 堆块:__NSMallocBlock__(或 NSMallocBlock)。

2. 根据是否捕获外部变量判断

• 无捕获:必定是全局块(无论是否 copy,因为没有需要存储的状态)。

• 有捕获:

在 定义时:若未执行 copy,且在栈作用域内,是栈块(ARC 下可能自动 copy 为堆块,需看使用场景)。

在 作为属性、参数传递或返回值时:ARC 会自动将栈块 copy 为堆块(例如 strong 或 copy 修饰的属性),此时是堆块。

3. 根据内存管理场景判断(ARC vs MRC)

• MRC 下:

栈块:未调用 copy,且在栈作用域内(如函数内定义未传递出去)。

堆块:手动调用 copy(如 [block copy])或作为 copy 修饰的属性值。

• ARC 下:

栈块:仅在定义后未被任何强引用持有,且未超出作用域时短暂存在(极少见,因为 ARC 会自动对需要延长生命周期的栈块执行 copy)。

堆块:几乎所有被强引用持有的 Block(如属性、集合对象中的 Block)都是堆块,因为 ARC 会自动 copy。

三、关键场景中的 Block 类型变化

1. 定义时的默认类型

• 无捕获:全局块(无论 ARC/MRC)。

• 有捕获:

MRC:栈块(需手动 copy 到堆)。

ARC:栈块,但当 Block 被赋值给强引用(如属性、变量)时,ARC 会自动 copy 为堆块。

2. 作为函数参数传递

• 若函数参数类型为 void (^)(void)(非 copy 修饰):

MRC:传递栈块,接收方需手动 copy 以延长生命周期。

ARC:传递时自动 copy 为堆块(底层调用 Block_copy)。

• 若函数参数使用 copy 修饰(如 GCD 的 dispatch_async):会强制将栈块 copy 为堆块。

3. 作为属性修饰符

• strong 修饰:ARC 下会自动 copy 栈块为堆块(等效于 copy,因为 Block 是对象,strong 对 Block 的效果和 copy 一致)。

• copy 修饰(推荐):显式确保 Block 被 copy 到堆区,避免栈块释放后野指针问题。

四、面试常见问题与答案

问题 1:如何判断一个 Block 是全局块、栈块还是堆块?

答案:

通过 [block class] 打印类名:

• 全局块(无捕获):类名为 __NSGlobalBlock__。

• 栈块(有捕获且未 copy):类名为 __NSStackBlock__(仅在 ARC 下短暂存在,或 MRC 未手动 copy 时)。

• 堆块(有捕获且被 copy):类名为 __NSMallocBlock__(ARC 下绝大多数场景如此,如作为属性、参数传递时)。

问题 2:ARC 下,栈块何时会被自动 copy 为堆块?

答案:

以下场景中,ARC 会自动将栈块 copy 到堆区:

1. 当 Block 被赋值给 strong/copy 修饰的属性或变量时。

2. 当 Block 作为参数传递给 GCD 函数(如 dispatch_async)、block_copy 等会触发 copy 的函数时。

3. 当 Block 被添加到集合对象(如 NSArray、NSDictionary)中时。

问题 3:MRC 下,栈块和堆块的内存管理有何区别?

答案:

• 栈块:存储在栈区,生命周期随作用域结束而释放,无需手动释放,也不能调用 retain/release。

• 堆块:通过 copy 操作创建,需手动调用 release(或 autorelease)释放内存,遵循引用计数规则。

问题 4:为什么 Block 属性通常用 copy 修饰?

答案:

• 在 MRC 下,确保栈块被 copy 到堆区,避免栈块作用域结束后被释放,导致野指针。

• 在 ARC 下,copy 和 strong 对 Block 的效果一致(都会自动 copy 栈块),但 copy 更清晰地表达了“确保 Block 存储在堆区”的意图,是更标准的做法。

总结

区分 Block 类型的核心是:

1. 是否捕获外部变量(决定是否为全局块)。

2. 是否执行过 copy 操作(栈块 → 堆块的关键)。

3. 内存管理环境(ARC/MRC)(影响 copy 的自动触发)。

实际开发中,ARC 下绝大多数 Block 都是堆块,只需关注捕获变量导致的循环引用问题;而 MRC 下需手动管理栈块的 copy 和内存释放。通过打印 class 或分析捕获行为,可快速判断 Block 类型。

相关文章

  • iOS block的类型

    block:栈块、堆块、全局块。 1.栈块 NSGlobalBlock 表示这个block是全局分配的。block...

  • 简单理解block的种类

    块(Block)分为三类: 栈块 堆块 全局块 1. 栈block 定义块的时候,其所占内存区域是分配在栈中的,而...

  • 2019-02-08

    栈块、堆块、全局块 (Block详解) 对于Block之前只是在用,对于栈,堆这块没有细入研究,今天抽空把”Eff...

  • block 全局块 栈块及堆块

    栈区(block) 定义块的时候,其所占的内存区域是分配在栈中的.块只在定义它的那个范围内有效.例如,下面这段代码...

  • 《Effective Objective-C 2.0编写高质量i

    37. 理解 “块” 这一概念 实例: 全局块、栈块及堆块 要点总结 块(block)是C、OC、C++中的词法闭...

  • 栈块 堆块 全局块

    编译器会给if 和 else 两个范围内的block 分配栈内存, 但是只要出了这个范围之后, 栈内存有可能被覆写...

  • 栈块、堆块、全局块

    编译器版本为Clang5,主要技术是ARC,参考来自Objective-C Automatic Reference...

  • 全局块、栈块及堆块

    栈块 void (^block)(); if(){ block = ^(){ NSLog(@"block a"...

  • 理解Block

    一、block其实是有类型的, 且一共有3种类型, 全局块, 栈块, 堆块: 1.__NSGlobalBlock_...

  • 全局块(global block)、栈块(stack block

    三种类型的 block: 1、NSGlobalBlock:存储在程序的数据区域,在 block 内部没有引用任何外...

网友评论

      本文标题:iOS中,如何判断一个 Block 是全局块、栈块还是堆块?

      本文链接:https://www.haomeiwen.com/subject/ieldijtx.html