可以带着以下问题来阅读本文
基础问题
- 什么是 Block?请举例说明 Block 的使用场景。
- Block 如何捕获外部变量?请解释 Block 捕获变量的规则。
- 请解释 Block 的内存管理,特别是在非 ARC 和 ARC 环境下的区别。
进阶问题
- __block修饰符有什么作用?使用它有什么注意事项?
- 请解释 Block 的三种类型及其特点。
高级问题
- 请解释 Block 的底层实现原理,包括如何从栈复制到堆。
- __forwarding指针的作用是什么?它如何支持- __block变量的内存管理?
- 为什么要设计__block,__frowarding,其原理是什么?
1. 什么是block?什么是Block调用?
- Block是将函数及其上下文封装起来的对象。Block调用既是函数调用。
2. Block 的内存结构
Block 在底层是一个 Objective-C 对象,其内存结构包含以下主要部分:
- 
Block 对象头部: - isa指针:指向 Block 类的指针,表明这是一个对象。
- flags:用于表示 Block 的一些属性,如是否已经被复制到堆上、是否有捕获的外部变量等。
- reserved:保留字段,目前未使用。
- invoke:函数指针,指向 Block 的执行代码,即 Block 体的实现。
 
- 
Block 描述信息 ( descriptor):- size:Block 结构体的大小。
- copy_helper和- dispose_helper:当 Block 从栈复制到堆时,这两个函数指针用于管理捕获的外部变量的内存(如果有的话)。
- signature:Block 的签名信息,用于描述 Block 的参数和返回值类型(可选)。
 
- 
捕获的外部变量: - Block 会捕获其定义时所在作用域的局部变量。这些变量的副本或引用会紧随 Block 对象头部和描述信息之后存储。
 
- 
内存结构示例 
假设有如下的 Block 定义:
            
            
              objective-c
              
              
            
          
          int a = 10;
void (^myBlock)(void) = ^{
    NSLog(@"Value of a: %d", a);
};其内存结构大致如下:
            
            
              diff
              
              
            
          
          +----------------------+
|       isa 指针       |
+----------------------+
|       flags         |
+----------------------+
|      reserved       |
+----------------------+
|      invoke         |
+----------------------+
|    descriptor       |
|      - size         |
|      - copy_helper  |
|      - dispose_helper|
|      - signature    |
+----------------------+
|  捕获的外部变量 a   |
+----------------------+3. block的内存管理
1. Block 的三种类型
Block 在内存中有三种类型,它们的内存管理方式不同:
- _NSConcreteGlobalBlock:全局 Block,存储在全局数据区,不需要手动管理内存。
- _NSConcreteStackBlock:栈 Block,存储在栈上,当离开定义它的作用域时会被自动销毁。
- _NSConcreteMallocBlock:堆 Block,存储在堆上,需要手动管理内存(在非 ARC 环境下)。
当 Block 发生拷贝时,不同类型的 Block 会有不同的行为和变化:
- 全局 Block(_NSConcreteGlobalBlock)
- 全局 Block 存储在全局数据区,不捕获任何外部变量。
- 当全局 Block 被复制时,实际上并不会发生真正的内存拷贝操作,而是简单地返回 Block 本身的指针。因此,全局 Block 的拷贝不会引起任何内存或状态的变化。
- 栈 Block(_NSConcreteStackBlock)
- 栈 Block 存储在栈上,它可能捕获外部变量。
- 当栈 Block 被复制到堆上时(例如,通过 Block_copy 函数或在 ARC 下赋值给强引用),会发生以下变化:
- 为 Block 分配堆内存,并将栈 Block 的内容(包括捕获的外部变量)复制到堆上的新位置。
- 如果 Block 捕获了 __block变量,这些变量也会被一起复制到堆上,并且它们的__forwarding指针会被更新,以确保后续对这些变量的访问和修改都指向堆上的副本。
- 复制后的 Block 成为堆 Block(_NSConcreteMallocBlock),其生命周期由引用计数管理。
 
- 堆 Block(_NSConcreteMallocBlock)
- 堆 Block 存储在堆上,它的生命周期由引用计数管理。
- 当堆 Block 被复制时,在 ARC 环境下,引用计数会增加,但不会发生实际的内存拷贝操作。在非 ARC 环境下,需要手动管理 Block 的引用计数和内存释放。
总结:
- 当 Block 发生拷贝时,全局 Block 不会发生变化,栈 Block 会被复制到堆上,并成为堆 Block,而堆 Block 的引用计数会增加。
4. block捕获变量
在 Objective-C 中,Block 可以捕获其定义时所在作用域的外部变量。这个特性使得 Block 能够访问并使用在其外部定义的变量。以下是 Block 捕获外部变量的一些细节:
捕获方式
- 
基本数据类型变量:对于基本数据类型(如 int、float 等)的局部变量,Block 会捕获其值的副本。这意味着在 Block 内部对这些变量的修改不会影响原始变量的值。 objective-cint value = 10; void (^myBlock)(void) = ^{ NSLog(@"Value inside block: %d", value); }; value = 20; myBlock(); // 输出 "Value inside block: 10"在这个例子中, value在 Block 内部的值仍然是 10,即使在 Block 外部value被修改为 20。
- 
对象类型变量:对于对象类型的局部变量,Block 会捕获对对象的强引用。这意味着在 Block 内部可以访问和修改对象的属性,这些修改会反映到原始对象上。 objective-cNSMutableArray *array = [NSMutableArray arrayWithObjects:@"a", @"b", nil]; void (^myBlock)(void) = ^{ [array addObject:@"c"]; }; myBlock(); NSLog(@"Array: %@", array); // 输出 "Array: (a, b, c)"在这个例子中, array在 Block 内部被修改,添加了一个新元素 "c"。
总结
Block 捕获外部变量的能力使得它们非常灵活和强大,特别是在异步编程和回调中。正确理解 Block 如何捕获和使用外部变量对于编写正确和高效的代码非常重要。
5. block如何修改外部变量值:__block
__block的示例
如果你需要在 Block 内部修改一个基本数据类型的局部变量的值,你可以使用 __block 修饰符。这样,Block 会通过引用而不是值来捕获这个变量,从而允许在 Block 内部对变量进行修改。
            
            
              objective-c
              
              
            
          
          __block int value = 10;
void (^myBlock)(void) = ^{
    value = 20; // 修改 __block 变量的值
};
myBlock();
NSLog(@"Value: %d", value); // 输出 "Value: 20"在这个例子中,使用 __block 修饰符允许 Block 修改 value 的值。
__block 是什么?
在 Objective-C 中,__block 是一个存储类型修饰符,用于修饰在 Block 中使用的变量。当一个变量被 __block 修饰时,它允许在 Block 内部对该变量进行修改。
原理
- 封装成结构体 :使用 __block修饰的变量会被编译器封装成一个结构体。这个结构体包含变量的值和一个__forwarding指针,用于支持变量从栈复制到堆的过程。
- 引用捕获 :与普通局部变量通过值捕获不同,__block变量是通过引用捕获的。这意味着 Block 内部访问的是__block变量的地址,而不是它的副本。
- 栈到堆的复制 :当 Block 被复制到堆上时(例如,赋值给强引用变量或作为函数返回值),所有被 Block 捕获的 __block变量也会被复制到堆上。复制过程中,__forwarding指针会被更新,指向堆上的变量副本。
为什么需要 __block?
- 修改外部变量 :在没有 __block修饰符的情况下,Block 只能捕获外部变量的值,而不能修改这些变量。__block允许在 Block 内部修改外部变量的值。
- 支持变量的生命周期 :当 Block 被复制到堆上时,__block变量也会被复制到堆上,以确保在 Block 的生命周期内,变量仍然有效。
- 简化内存管理 :在自动引用计数(ARC)环境下,__block变量的内存管理会被自动处理,包括当变量被复制到堆上时的内存管理。
- 适应异步编程模式 :在异步编程中,经常需要在 Block 中修改外部变量以存储异步操作的结果。__block使得这种模式更加容易实现。
总的来说,__block 修饰符是为了增强 Block 的功能,使其能够修改捕获的外部变量。这在异步编程、回调处理以及其他需要在 Block 中修改外部状态的场景中非常有用。了解 __block 的原理和作用有助于更有效地使用 Block 和管理内存。
6. __forwarding
__forwarding 是一个在 Objective-C 中与 __block 变量相关的机制。它是 __block 变量结构体中的一个指针,用于确保无论变量是在栈上还是堆上,对该变量的访问和修改都能正确进行。
原理
- __block变量的封装 :使用- __block修饰符声明的变量会被编译器封装成一个结构体,该结构体包含变量的值和一个- __forwarding指针。
- 指针指向自身 :初始时,__block变量的__forwarding指针指向变量自身(即指向结构体本身)。
- 复制到堆上 :当 Block 被复制到堆上时,所有被 Block 捕获的 __block变量也会被复制到堆上。此时,__forwarding指针会被更新,指向堆上的变量副本。
- 统一访问方式 :无论 __block变量是在栈上还是堆上,对它的访问和修改都通过__forwarding指针进行。这确保了变量的访问和修改总是指向正确的存储位置。
为什么需要 __forwarding
- 保持访问一致性 :__forwarding机制确保在 Block 中对__block变量的访问和修改总是一致的,无论变量是在栈上还是堆上。
- 支持栈到堆的迁移 :当 Block 和 __block变量从栈复制到堆时,__forwarding指针使得变量的迁移过程更加平滑。它保持了对变量的引用不变,即使变量的存储位置发生了变化。
- 简化内存管理 :__forwarding机制简化了__block变量在栈和堆之间迁移时的内存管理。开发者不需要关心变量的具体存储位置,只需通过__forwarding指针进行访问和修改。
总的来说,__forwarding 是 __block 变量机制的一个重要组成部分,它确保了在 Block 中使用 __block 变量时的访问一致性和内存管理的简化。了解 __forwarding 的原理和作用有助于更深入地理解 Block 和 __block 变量的工作机制。
总结
__block 修饰符的原理涉及到 __block 变量的存储方式、捕获机制和内部结构。通过将变量封装为结构体并通过引用捕获,__block 允许在 Block 中修改外部变量的值。在 Block 被复制到堆上时,__forwarding 指针确保对变量的访问和修改都通过同一个地址进行,确保了数据的一致性和正确性。