返回

理解 iOS 中的 Block 底层原理:全面剖析其类型和循环引用解决方法

IOS

揭秘 Block 的类型

在 iOS 中,block 是轻量级的代码块,可以捕获周围上下文的变量,并在稍后执行。它们主要分为以下三种类型:

1. NSGlobalBlock:全局 Block

全局 block 存储在全局数据区中,不依赖于创建它们的堆栈帧。因此,它们的生命周期与应用程序的生命周期相同,即使捕获的变量超出作用域,它们仍然可用。

2. NSMallocBlock:堆 Block

堆 block 分配在堆内存中,并且在 block 不再需要时被自动释放。它们可以捕获堆栈上的变量,但当变量超出作用域时,它们将变为无效。

3. NSStackBlock:栈 Block

栈 block 分配在栈内存中,并且在函数返回时被自动释放。它们只能捕获在创建它们的栈帧中声明的变量。

巧解 Block 中的循环引用

循环引用是指两个或多个对象相互引用,导致内存泄漏。在使用 block 时,需要注意以下两种常见的循环引用场景:

1. 强引用循环

当 block 捕获了一个对自身强引用的对象时,就会发生强引用循环。例如:

__strong id strongObject = ^{
  // strongObject 被捕获并持有 block
};

在这种情况下,strongObject 对 block 具有强引用,而 block 又对 strongObject 具有强引用,形成一个循环。为了解决这个问题,可以使用 __weak 或 __unsafe_unretained 来声明对对象的弱引用。

2. Block 捕获循环

当两个或多个 block 相互捕获时,就会发生 block 捕获循环。例如:

__block id block1 = ^{
  // block1 捕获了 block2
  __block id block2 = ^{
    // block2 捕获了 block1
  };
};

为了打破这种循环,可以在 block 中使用 __weak 或 __block 修饰符。

窥探 Block 的底层机制

在底层,block 是通过一个称为 Block 符(BDR)的数据结构实现的。BDR 包含有关 block 类型、捕获变量、函数指针等信息。当 block 被调用时,BDR 用于确定要执行的代码和访问捕获的变量。

在 ARC(自动引用计数)下,block 的内存管理是自动的。当 block 不再需要时,ARC 会自动释放它。但是,在手动内存管理的情况下,开发者需要自己管理 block 的生命周期。

总结

对 block 类型、循环引用解决方法以及底层机制的深入理解对于编写高效、可靠的 iOS 代码至关重要。通过应用这些原则,开发者可以避免内存泄漏、提高性能并确保代码的可维护性。