可以带着以下问题来阅读本文

根底问题

  1. 什么是 Block?请举例说明 Block 的运用场景。
  2. Block 怎么捕获外部变量?请解说 Block 捕获变量的规矩。
  3. 请解说 Block 的内存办理,特别是在非 ARC 和 ARC 环境下的区别。

进阶问题

  1. __block 润饰符有什么效果?运用它有什么注意事项?
  2. 请解说 Block 的三种类型及其特色。

高档问题

  1. 请解说 Block 的底层完成原理,包括怎么从栈仿制到堆。
  2. __forwarding 指针的效果是什么?它怎么支撑 __block 变量的内存办理?
  3. 为什么要设计__block,__frowarding,其原理是什么?

1. 什么是block?什么是Block调用?

  • Block是将函数及其上下文封装起来的目标。Block调用既是函数调用

2. Block 的内存结构

Block 在底层是一个 Objective-C 目标,其内存结构包括以下首要部分:

  1. Block 目标头部

    • isa 指针:指向 Block 类的指针,表明这是一个目标。
    • flags:用于表示 Block 的一些特点,如是否现已被仿制到堆上、是否有捕获的外部变量等。
    • reserved:保留字段,目前未运用。
    • invoke:函数指针,指向 Block 的执行代码,即 Block 体的完成。
  2. Block 描绘信息descriptor):

    • size:Block 结构体的巨细。
    • copy_helperdispose_helper:当 Block 从栈仿制到堆时,这两个函数指针用于办理捕获的外部变量的内存(假如有的话)。
    • signature:Block 的签名信息,用于描绘 Block 的参数和返回值类型(可选)。
  3. 捕获的外部变量

    • Block 会捕获其界说时地点效果域的局部变量。这些变量的副本或引证会紧随 Block 目标头部和描绘信息之后存储
  4. 内存结构示例

假设有如下的 Block 界说:

int a = 10;
void (^myBlock)(void) = ^{
    NSLog(@"Value of a: %d", a);
};

其内存结构大致如下:

+----------------------+
|       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 会有不同的行为和改变:

  1. 大局 Block(_NSConcreteGlobalBlock)
  • 大局 Block 存储在大局数据区,不捕获任何外部变量。
  • 当大局 Block 被仿制时,实践上并不会产生真实的内存仿制操作,而是简略地返回 Block 本身的指针。因而,大局 Block 的仿制不会引起任何内存或状况的改变。
  1. 栈 Block(_NSConcreteStackBlock)
  • 栈 Block 存储在栈上,它可能捕获外部变量。
  • 当栈 Block 被仿制到堆上时(例如,经过 Block_copy 函数或在 ARC 下赋值给强引证),会产生以下改变:
    • 为 Block 分配堆内存,并将栈 Block 的内容(包括捕获的外部变量)仿制到堆上的新方位。
    • 假如 Block 捕获了 __block 变量,这些变量也会被一起仿制到堆上,并且它们的 __forwarding 指针会被更新,以保证后续对这些变量的拜访和修正都指向堆上的副本。
    • 仿制后的 Block 成为堆 Block(_NSConcreteMallocBlock),其生命周期由引证计数办理。
  1. 堆 Block(_NSConcreteMallocBlock)
  • 堆 Block 存储在堆上,它的生命周期由引证计数办理。
  • 当堆 Block 被仿制时,在 ARC 环境下,引证计数会添加,但不会产生实践的内存仿制操作。在非 ARC 环境下,需求手动办理 Block 的引证计数和内存开释。

总结:

  • 当 Block 产生仿制时,大局 Block 不会产生改变,栈 Block 会被仿制到堆上,并成为堆 Block,而堆 Block 的引证计数会添加。

4. block捕获变量

在 Objective-C 中,Block 可以捕获其界说时地点效果域的外部变量。这个特性使得 Block 可以拜访并运用在其外部界说的变量。以下是 Block 捕获外部变量的一些细节:

捕获方法

  1. 根本数据类型变量:对于根本数据类型(如 int、float 等)的局部变量,Block 会捕获其值的副本。这意味着在 Block 内部对这些变量的修正不会影响原始变量的值。

    int value = 10;
    void (^myBlock)(void) = ^{
        NSLog(@"Value inside block: %d", value);
    };
    value = 20;
    myBlock(); // 输出 "Value inside block: 10"
    

    在这个比如中,value 在 Block 内部的值依然是 10,即便在 Block 外部 value 被修正为 20。

  2. 目标类型变量:对于目标类型的局部变量,Block 会捕获对目标的强引证。这意味着在 Block 内部可以拜访和修正目标的特点,这些修正会反映到原始目标上。

    NSMutableArray *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 内部对变量进行修正。

__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 内部对该变量进行修正。

原理

  1. 封装成结构体:运用 __block 润饰的变量会被编译器封装成一个结构体。这个结构体包括变量的值和一个 __forwarding 指针,用于支撑变量从栈仿制到堆的进程。
  2. 引证捕获:与普通局部变量经过值捕获不同,__block 变量是经过引证捕获的。这意味着 Block 内部拜访的是 __block 变量的地址,而不是它的副本。
  3. 栈到堆的仿制:当 Block 被仿制到堆上时(例如,赋值给强引证变量或作为函数返回值),一切被 Block 捕获的 __block 变量也会被仿制到堆上。仿制进程中,__forwarding 指针会被更新,指向堆上的变量副本。

为什么需求 __block?

  1. 修正外部变量:在没有 __block 润饰符的情况下,Block 只能捕获外部变量的值,而不能修正这些变量。__block 允许在 Block 内部修正外部变量的值。
  2. 支撑变量的生命周期:当 Block 被仿制到堆上时,__block 变量也会被仿制到堆上,以保证在 Block 的生命周期内,变量依然有效。
  3. 简化内存办理:在主动引证计数(ARC)环境下,__block 变量的内存办理会被主动处理,包括当变量被仿制到堆上时的内存办理。
  4. 习惯异步编程模式:在异步编程中,常常需求在 Block 中修正外部变量以存储异步操作的结果。__block 使得这种模式愈加容易完成。

总的来说,__block 润饰符是为了增强 Block 的功能,使其可以修正捕获的外部变量。这在异步编程、回调处理以及其他需求在 Block 中修正外部状况的场景中非常有用。了解 __block 的原理和效果有助于更有效地运用 Block 和办理内存。

6. __forwarding

__forwarding 是一个在 Objective-C 中与 __block 变量相关的机制。它是 __block 变量结构体中的一个指针,用于保证不管变量是在栈上仍是堆上,对该变量的拜访和修正都能正确进行。

原理

  1. __block 变量的封装:运用 __block 润饰符声明的变量会被编译器封装成一个结构体,该结构体包括变量的值和一个 __forwarding 指针。
  2. 指针指向本身:初始时,__block 变量的 __forwarding 指针指向变量本身(即指向结构体本身)。
  3. 仿制到堆上:当 Block 被仿制到堆上时,一切被 Block 捕获的 __block 变量也会被仿制到堆上。此时,__forwarding 指针会被更新,指向堆上的变量副本。
  4. 共同拜访方法:不管 __block 变量是在栈上仍是堆上,对它的拜访和修正都经过 __forwarding 指针进行。这保证了变量的拜访和修正总是指向正确的存储方位。

为什么需求 __forwarding

  1. 坚持拜访共同性__forwarding 机制保证在 Block 中对 __block 变量的拜访和修正总是共同的,不管变量是在栈上仍是堆上。
  2. 支撑栈到堆的迁移:当 Block 和 __block 变量从栈仿制到堆时,__forwarding 指针使得变量的迁移进程愈加滑润。它坚持了对变量的引证不变,即便变量的存储方位产生了改变。
  3. 简化内存办理__forwarding 机制简化了 __block 变量在栈和堆之间迁移时的内存办理。开发者不需求关怀变量的具体存储方位,只需经过 __forwarding 指针进行拜访和修正。

总的来说,__forwarding__block 变量机制的一个重要组成部分,它保证了在 Block 中运用 __block 变量时的拜访共同性和内存办理的简化。了解 __forwarding 的原理和效果有助于更深入地了解 Block 和 __block 变量的工作机制。

总结

__block 润饰符的原理涉及到 __block 变量的存储方法、捕获机制和内部结构。经过将变量封装为结构体并经过引证捕获,__block 允许在 Block 中修正外部变量的值。在 Block 被仿制到堆上时,__forwarding 指针保证对变量的拜访和修正都经过同一个地址进行,保证了数据的共同性和正确性。