Files
knowledge-kit/Chapter1 - iOS/1.89.md
2022-05-24 13:00:23 +08:00

902 lines
27 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# block 底层原理
## block 本质
block 本质上就是一个 oc 对象,也有 isa 指针
block 是封装了函数调用和函数调用环境的 OC 对象
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/block-structure.png)
```objectivec
int age = 27;
void(^block)(void) = ^(){
    NSLog(@"age:%d", age);
};
block();
```
`xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp`
转换为 c++ 如下
```c
int age = 27;
void(*block)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
```
可以看到 `__main_block_impl_0` 是一个结构体有3个变量其中 `__main_block_impl_0` 是一个构造方法接收4个参数第四个参数 flags 是非必须的。
`__main_block_func_0` 参数是一个方法实现。
```c
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_main_eb3c55_mi_0, age);
}
```
```c
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
```
`__main_block_desc_0` 是一个 block 信息的描述,占用了 `sizeof(struct __main_block_impl_0)` 大小的空间。
`void(*block)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));` 第一行代码也就是将构造一个 struct 给 block 变量。
`((**void** (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);` 第二行代码其实就是 `block->FuncPtr(block)` 根据 block 内部 FuncPtr 方法并调用。
```c
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
```
为什么 block->FuncPtr 可以直接访问,而不是 block 先访问 impl再访问 FuncPtr因为 __block_impl 就是 __main_block_impl_0 这个结构体的第一个变量地址(结构体特性)
```c
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
```
类似于下面代码
```
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
```
## block 变量捕获
```objectivec
int age = 27;
void(^block)(void) = ^(){
NSLog(@"age:%d", age);
};
age = 30;
block();
```
输出27。因为 Block 会对变量进行捕获。
```c
int age = 27;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 30;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
```
变量分为static、auto、register。
static表示作为静态变量存储在数据区。
auto一般的变量不加修饰词则默认为 autoauto 表示作为自动变量存储在栈上。意味着离开作用域变量会自动销毁。
register这个关键字告诉编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率。是尽可能不是绝对。如果定义了很多 register 变量可能会超过CPU 的寄存器个数,超过容量。所以只是可能。
| 作用域 | 捕获到 block 内部 | 访问方式 |
| ---------- | ------------ | ---- |
| 局部变量 auto | YES | 值传递 |
| 局部变量static | YES | 指针传递 |
| 全局变量 | NO | 直接访问 |
Demo2
```objectivec
auto int age = 27;
static int height = 176;
void(^block)(void) = ^(){
NSLog(@"age:%d, height: %d", age, height);
};
age = 30;
height = 177;
block();
// age:27, height: 177
```
clang 转为 c++
```c
auto int age = 27;
static int height = 176;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 30;
height = 177;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
```
可以看到 static 修饰的 height 在 c++ 代码底层用指针被 block 捕获,所以值修改后,是最终的 177static 修饰的 age被 block 捕获是值传递方式所以还是27
Demo3
```objectivec
int age = 27;
static int height = 176;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^(){
NSLog(@"age:%d, height: %d", age, height);
};
age = 26;
height = 177;
block();
}
return 0;
}
```
转为 c++
```c
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
age = 26;
height = 177;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_main_65da50_mi_0, age, height);
}
```
可以看到全局变量是直接访问的,不进行拷贝。
为什么这么设计?
因为 auto 修饰的变量一出作用域马上回收,所以 block 为了自身运行信息的完整性所以会捕获。static 修饰的变量,数据一直在内存中,所以执行 block 代码的时候是用指针获取内存中最新的数据。全局变量不会捕获。
self 会捕获吗?
会,因为 self 就是局部变量。 一个 oc 方法转换为 `void test(Person *self, SEL _cmd)` 形式,所以 self 也是局部变量,会被捕获
## block 类型
block 的类型可以通过 isa 或者 class 方法查看,最终都是继承自 NSBlock 类型
`__NSGlobalBlock__` (`_NSConcreteGlobalBlock`):程序的数据区域(.data 区)
`__NSStackBlock__` (`_NSConcreteStackBlock`)
`__NSMallocBlock__`(`_NSConcreteMallockBlock`)
```objectivec
void(^block)(void) = ^(){
NSLog(@"Hello block");
};
NSLog(@"%@", [block class]); // __NSGlobalBlock__
NSLog(@"%@", [[block class] superclass]); // NSBlock
NSLog(@"%@", [[[block class] superclass] superclass]); // NSObjec
```
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/block-memorylayout.png)
代码存放在 text 段static 修饰的数据存放在 data 区,程序员手动申请的内存存放在堆,局部变量存放在栈区
### 如何判断 block 属于什么类型
Demo1
```objectivec
void(^block)(void) = ^{
NSLog(@"");
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%@", [block class]);
}
return 0;
}
```
`__NSGlobalBlock__` ,此**类型的 block 用结构体实例的内容不依赖于执行时的状态所以整个程序只需要1个实例。因此存放于全局变量相同的数据区域即可。**
```objectivec
void(^block)(void);
void test()
{
int age = 22;
block = ^ {
NSLog(@"ag:%d", age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block(); // age:-1074793800
}
return 0;
}
```
为什么会打印出 `age:-1074793800`。 因为 block 访问了auto 变量,所以是 `__NSStackBlock__`。那么 block 内部的数据在栈上维护,当出了 test 方法后block 内部变量的地址到底指向什么是不确定的,可能会出现异常。
| block 类型 | 环境 |
| ------------------- | ------------------------------ |
| `__NSGlobalBlock__` | 没有访问 auto 变量 |
| `__NSStackBlock__` | 访问了 auto 变量 |
| `__NSMallocBlock__` | `__NSStackBlock__` 调用了 copy 方法 |
Demo1:
```objectivec
MyBlock block;
{
Person *person = [[Personalloc] init];
block = ^{
NSLog(@"block called");
};
NSLog(@"%@", [block class]);
};
```
MRC 环境: 如果 block 不访问外部局部变量,则`__NSGlobalBlock__`
ARC 环境:如果 block 不访问外部局部变量,则`__NSGlobalBlock__`
Demo2:
```objectivec
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
{
auto Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"age:%zd", person.age);
};
NSLog(@"%@", [block class]); // __NSStackBlock__
};
}
return 0;
}
```
MRC 环境下:如果访问了 auto 变量,则为 `__NSStackBlock__`
ARC 环境下:**ARC 下面比较特殊,默认局部变量对象都是强指针,存放在堆里面。所以 block 为 `__NSMallocStack__`**
Demo3:
```objectivec
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
{
auto Person *person = [[Person alloc] init];
person.age = 10;
block = [^{
NSLog(@"age:%zd", person.age);
} copy];
NSLog(@"%@", [block class]); // __NSMallocBlock__
};
}
return 0;
}
```
MRC 下:如果 block 调用 copy 方法,则 block 为 `__NSMallocStck__`
ARC 下:如果 block 调用 copy 方法,则 block 仍旧为 `__NSMallocBlock__``__NSMallocBlock__` 调用 copy 仍旧为 `__NSMallocBlock__`
在 ARC 下,如果有一个强指针引用 block则 block 会被拷贝到堆上,成为 `__NSMallocStck`
| Block 类 | 原本位置 | 复制效果 | |
| --------------------------- | ------ | ------ | --- |
| `__NSConcreteStackBlock__` | 栈 | 栈复制到堆 | |
| `__NSConcreteGlobalBlock__` | 程序的数据段 | 什么也不做 | |
| `__NSConcreteMallocBlock__` | 堆 | 引用计数+1 | |
### 内存管理
### 思考题
查看 block 编译成 c++ 代码的源码可以发现 `__main_block_desc_0` 结构体内部是变化的。什么意思呢reserved、Block_size 是一直有的void (*copy)、void (*dispose) 只有在修饰对象的时候才有。为什么这么设计?
```c
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
```
因为 block 会对变量进行内存管理。`void *copy``void *dispose` 都是内存管理的方法。
如果 block 访问的不是对象,则结构体没有 `void *copy``void *dispose`
Demo1:
```objectivec
{
Person *person = [[Person alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1---%@", person);
});
}
```
1s 后 person 执行了 dealloc 方法
Demo2
```objectivec
{
__weak Person *person = [[Person alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1---%@", person);
});
}
```
马上执行了 Person 的 dealloc 方法。因为 `__weak` 修饰block 内部的 `_Block_object_assign` 会根据 `__strong` 为对象引用计数 +1`__weak` 则引用计数不变。所以是 `__weak` 修饰,出离作用域则立马会释放 Person 对象。
`_Block_object_assign` 会根据内存修饰符来对内存进行操作。
Demo3
```objectivec
{
Person *person = [[Person alloc] init];
__weak Person *weakP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1---%@", person);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2---%@", person);
});
});
}
```
3s 后执行 Person 的 dealloc
Demo4
```objectivec
{
Person *person = [[Person alloc] init];
__weak Person *weakP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1---%@", weakP);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2---%@", person);
});
});
}
```
3s 后执行 Person 的 dealloc 方法
Demo5
```objectivec
{
Person *person = [[Person alloc] init];
__weak Person *weakP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1---%@", person);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2---%@", weakP);
});
});
}
```
1s 后执行 Person 的 dealloc 方法。
结论block 作为 GCD API 的方法参数时,如果 block 内部访问了对象,对象的生命周期结束需要查看强引用结束时刻。
### ARC 环境下编译器会自动会 block copy 复制到堆上
1. block 作为函数返回值时(如果栈 block离开方法作用域之后return 给新的变量区使用,由于栈变化了,所以不安全。比如访问 auto 变量的栈 block可能某个变量已经不是之前的某个值了
2. 将 block 赋值给 __strong 指针时
3. block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时
4. block 作为 GCD API 的方法参数时
栈空间的 block 不会对变量进行 copy 操作
堆空间的 block 会堆变量自动进行 copy 操作
`__NSStackBlock__` 内部访问了对象,默认是 `__strong` 修饰。如果对象是 `__weak` 则 block 转换 c++ 内部捕获的对象,也用 weak 修饰
```c
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
{
__weak Person *person = [[Person alloc] init];
person.age = 27;
block = ^{
NSLog(@"age:%zd", person.age);
};
person.age = 28;
};
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
```
## block 修改变量
Demo1
```objectivec
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 27;
MyBlock block = ^{
age = 28;
};
NSLog(@"%zd", age);
}
return 0;
}
```
运行会报错 `// Variable is not assignable (missing __block type specifier)` 为什么不能修改?继续查看 c++ 源代码
```c
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_main_f31a48_mi_0, age);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 27;
MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_main_f31a48_mi_1, age);
}
return 0;
}
```
可以看到在 block 内部修改外部变量也就是在新创建的函数内部,修改 main 函数内部的变量 😂 这怎么可能?
全局变量、static 变量在 block 内部可以修改。
- `__block` 用于解决 block 内部无法修改 auto 变量的问题。
- `__block` 不能修饰 static、全局变量
- 编译器会将 `__block` 修饰的变量包装为一个对象(后续修改则通过指针找到结构体对象,结构体对象再修改里面的值)
Demo
```objectivec
__block int age = 27;
MyBlock block = ^{
age = 28;
};
```
转为 C++
```c
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 27};
MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_main_f0f60a_mi_0, (age.__forwarding->age));
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 28;
}
```
可以看到 `__block int age = 27;` 变为了 `__Block_byref_age_0 age` 结构体。block 内部的函数在修改 age 的时候其实就是通过 `__main_block_impl_0` 结构体的 age 找到 `__Block_byref_age_0`,然后访问 `__Block_byref_age_0` 中的成员变量 `__forwarding` 访问成员变量 age并修改值。
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/block-forwarding.png)
`__block` 修饰基本数据类型和对象,对于生成的结构体也不一样。
QA为什么`__block` 变量的 `__Block_byref_age_0` 结构体并不在 block 结构体 `__main_block_impl_0` 中?
因为这样做可以在多个 block 中使用 `__block` 变量。
Demo
```objectivec
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __Block_byref_p_1 {
void *__isa;
__Block_byref_p_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *p;
};
```
block 只要和对象打交道,结构体里面要管理内存,所以会有 `void *copy` ,`void *dispose`
block 内部操作数组等类型,不需要加 `__block`
Demo知道 `__block` 的本之后,下面打印的 age 的地址是 struct 里面哪个的值?
```objectivec
__block int age = 27;
MyBlock block = ^{
age = 28;
};
NSLog(@"%p", &age);
```
知道转换为c++后的效果,我们可以在代码中按照结构体,自己定义并转接到 block
```objectivec
struct __Block_byref_age_0 {
void *__isa; // 0x0000000105231f70 +8
struct __Block_byref_age_0 *__forwarding; // 0x0000000105231f78 + 8
int __flags; // 0x0000000105231f80 +4
int __size; // 0x0000000105231f84 + 4
int age; // 0x0000000105231f88
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age; // by ref
};
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 27;
MyBlock block = ^{
age = 28;
};
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p", &age);
}
return 0;
}
```
我们将断点设置到 NSLog 这里,打印出自定义结构体 `__main_block_impl_0` 中的 age 。
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/Block-variableAddress.png)
```c
// 0x0000000105231f70
struct __Block_byref_age_0 {
void *__isa; // 地址0x0000000105231f70 长度:+8
struct __Block_byref_age_0 *__forwarding; // 地址0x0000000105231f78 长度:+8
int __flags; // 地址0x0000000105231f80 长度:+4
int __size; // 地址0x0000000105231f84 长度:+4
int age; // 地址0x0000000105231f88
};
```
将地址打印出来。该地址就是 `__Block_byref_age_0` 结构体的地址,也就是结构体内第一个 `isa` 的地址。我们计算下,规则如下:
- 指针长度8个字节
- int 长度4个字节
算出来 age 的地址为 `0x0000000105231f88` ,此时 Xcode 打印出的地址也是 `0x105231f88`。其实也就是 `blockImple->age->age` 的地址
block 内部对变量的值修改其实就是对 block 内部自定义结构体内部的变量修改。
当 block 被 copy 到堆上
- 会调用 block 内部的 copy 函数
- copy 函数内部会调用 `_Block_object_assign` 函数
- `_Block_object_assign` 函数会根据所指向对象的修饰符__strong、__weak、__unsafe_unretained做出相应的操作形成强引用retain或者弱引用注意这里仅限于ARC时会retainMRC时不会retain
当 block 从堆中移除
- 会调用 block 内部的 dispose 函数
- dispose 函数会调用 `_Block_object_dispose` 函数
- `_Block_object_dispose` 函数会自动释放 `__block` 修饰的变量release
## `__forwarding` 的设计
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/block_forwarding.png)
当block在栈中时`__Block_byref_age_0`结构体内的`__forwarding`指针指向结构体自己。
而当block被复制到堆中时栈中的`__Block_byref_age_0`结构体也会被复制到堆中一份,而此时栈中的`__Block_byref_age_0`结构体中的`__forwarding`指针指向的就是堆中的`__Block_byref_age_0`结构体,堆中`__Block_byref_age_0`结构体内的`__forwarding`指针依然指向自己。
## Block 内存引用
`__block ` 修饰符修饰的对象在内存中如下
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/block-object-memoery.png)
```c
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_p_0 p = {(void*)0,(__Block_byref_p_0 *)&p, 33554432, sizeof(__Block_byref_p_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_p_0 *)&p, 570425344));
}
return 0;
}
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->p, (void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);
}
```
如果 `__block` 修饰 `__strong` 则表示 block_impl 结构体中的 person 成员变量指向一个新的结构体 `__Block_byref_person_0`。这个线是强引用。
`__Block_byref_person_0` 结构体成员变量 person 真正的 Person 对象的引用关系要看 block 外部 person 的修饰是 `__strong` 还是 `__weak`,因为从栈上拷贝到堆上,会调用 block 的 desc 的 `__main_block_copy_0`,本质上调用的是 `_Block_object_assign`
`__Block_byref_id_object_copy_131` 方法里的 40 代表什么?
```c
struct __Block_byref_p_0 {
void *__isa; 8
__Block_byref_p_0 *__forwarding; 8
int __flags; 4
int __size; 4
void (*__Block_byref_id_object_copy)(void*, void*); 8
void (*__Block_byref_id_object_dispose)(void*); 8
Person *p;
};
__attribute__((__blocks__(byref))) __Block_byref_p_0 p = {
0,
&p,
33554432,
sizeof(__Block_byref_p_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))
};
```
`__Block_byref_p_0` 结构体地址上偏移40就是 p 对象。
## 循环引用
self 是一个局部变量block 访问 self即存在捕获变量的效果。
### ARC 下
`__weak``__unsafe_unretained``__block``
区别在于:`__weak` 不会产生强引用,指向的对象销毁时,会自动给指针置为 nil
`__unsafe_retained` 不会产生强引用,不安全。当指向的对象销毁时,指针地址值不变。
```objectivec
@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) void (^block)(void);
- (void)test;
@end
@implementation Person
- (void)dealloc
{
NSLog(@"%s", __func__);
}
- (void)test
{
    
__weak typeof(self) weakself = self;
self.block = ^{
weakself.age = 23;
};
self.block();
NSLog(@"age:%ld", (long)self.age);
}
@end
Person *p = [[Person alloc] init];
[p test];
```
方法1: `__weak` 修饰。`__weak typeof(self) weakself = self;`
方法2: `__unsafe_retained` 修饰。`**__unsafe_unretained** **typeof**(**self**) weakself = **self**;`
方法3: `__block` 修饰。因为此时会构成3角关系。所以需要调用 block。block 内部需要将对象设置为 nil。
```objectivec
__block Person *weakself = [[Person alloc] init];
p.block = ^{
weakself.age = 23;
NSLog(@"%ld", weakself.age);
weakself = nil;
};
p.block();
```
`__unsafe_retained` 因为不安全所以不推荐,`__block` 因为使用繁琐,且必须等到调用 block 才会释放内存所以不推荐。ARC 下最佳用 `__weak`
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/block_object_cycle.png)
### MRC 下
方法1: `__unsafe_retained` 修饰。`**__unsafe_unretained** **typeof**(**self**) weakself = **self**;`
方法2: `__block` 修饰。MRC 下不会对 block 内部的对象引用计数 +1
## 总结
block 本质是什么?
封装了函数调用及其调用环境的 OC 对象
`__block` 的作用是什么?
可以对 block 外部的变量进行捕获,可以修改。但是需要注意内存管理相关问题。比如`__weak`、`__unsafe_unretained`、`__block`
修改 NSMutableArray 不需要加 `__block`?
是的