mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 12:27:15 +00:00
902 lines
27 KiB
Markdown
902 lines
27 KiB
Markdown
# block 底层原理
|
||
|
||
## block 本质
|
||
|
||
block 本质上就是一个 oc 对象,也有 isa 指针
|
||
|
||
block 是封装了函数调用和函数调用环境的 OC 对象
|
||
|
||

|
||
|
||
```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:一般的变量不加修饰词则默认为 auto,auto 表示作为自动变量存储在栈上。意味着离开作用域变量会自动销毁。
|
||
|
||
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 捕获,所以值修改后,是最终的 177,static 修饰的 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
|
||
```
|
||
|
||

|
||
|
||
代码存放在 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,并修改值。
|
||
|
||

|
||
|
||
`__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 。
|
||
|
||

|
||
|
||
```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时会retain,MRC时不会retain)
|
||
|
||
当 block 从堆中移除
|
||
|
||
- 会调用 block 内部的 dispose 函数
|
||
|
||
- dispose 函数会调用 `_Block_object_dispose` 函数
|
||
|
||
- `_Block_object_dispose` 函数会自动释放 `__block` 修饰的变量(release)
|
||
|
||
|
||
|
||
## `__forwarding` 的设计
|
||
|
||

|
||
|
||
当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 ` 修饰符修饰的对象在内存中如下
|
||
|
||

|
||
|
||
```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`
|
||
|
||

|
||
|
||
### 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`?
|
||
|
||
是的 |