一文彻底理解iOS block的知识点

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

基础问题

  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 定义:

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

  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 内部对这些变量的修改不会影响原始变量的值。

    objective-c 复制代码
    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 内部可以访问和修改对象的属性,这些修改会反映到原始对象上。

    objective-c 复制代码
    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 内部对变量进行修改。

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

原理

  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 指针确保对变量的访问和修改都通过同一个地址进行,确保了数据的一致性和正确性。

相关推荐
小江村儿的文杰18 小时前
UE4 iOS Package的过程与XCode工程中没有游戏Content的原因
macos·ios·ue4·xcode
__WanG21 小时前
Flutter将应用打包发布到App Store
前端·flutter·ios
安和昂1 天前
【iOS】bug调试技巧
ios·bug·cocoa
emperinter1 天前
WordCloudStudio Now Supports AliPay for Subscriptions !
人工智能·macos·ios·信息可视化·中文分词
AirDroid_cn1 天前
iPhone或iPad接收的文件怎么找?怎样删除?
ios·iphone·ipad·文件传输
Swift社区1 天前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
#摩斯先生1 天前
Swift从0开始学习 对象和类 day3
ios·xcode·swift
没头脑的ht1 天前
Swift内存访问冲突
开发语言·ios·swift
#摩斯先生1 天前
Swift从0开始学习 并发性 day4
ios·xcode·swift
没头脑的ht1 天前
Swift闭包的本质
开发语言·ios·swift