可以带着以下问题来阅读本文
根底问题
- 什么是 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 界说:
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 会有不同的行为和改变:
- 大局 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 内部对这些变量的修正不会影响原始变量的值。
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。 -
目标类型变量:对于目标类型的局部变量,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 内部对该变量进行修正。
原理
-
封装成结构体:运用
__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
指针保证对变量的拜访和修正都经过同一个地址进行,保证了数据的共同性和正确性。