mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 20:00:40 +00:00
feature: Weex APM
This commit is contained in:
@@ -11,9 +11,9 @@
|
||||
|
||||
|
||||
|
||||
## block 本质探索
|
||||
## 一、block 本质探索
|
||||
|
||||
### 实验探索
|
||||
### 1. 实验探索
|
||||
|
||||
Demo
|
||||
|
||||
@@ -244,7 +244,7 @@ struct __ViewController__viewDidLoad_block_desc_0 {
|
||||
|
||||
|
||||
|
||||
### 结论
|
||||
### 2. 结论
|
||||
|
||||
通过探索发现:
|
||||
|
||||
@@ -260,9 +260,9 @@ struct __ViewController__viewDidLoad_block_desc_0 {
|
||||
|
||||
|
||||
|
||||
## block 变量捕获
|
||||
## 二、block 变量捕获
|
||||
|
||||
### auto 变量捕获
|
||||
### 1. auto 变量捕获
|
||||
|
||||
> 在 C 和 Objective-C 编程语言中,`auto` 关键字用于声明自动存储期的变量。自动存储期的变量会在定义它们的块(block)或作用域(scope)中自动创建,并在退出该作用域时自动销毁。这是变量存储期的默认行为,因此 `auto` 关键字实际上是可选的,但有时候为了清晰起见,开发者可能会显式使用它。
|
||||
|
||||
@@ -322,7 +322,7 @@ c++ 中,在函数内部定义的变量,默认用 **auto** 修饰,叫做自
|
||||
|
||||
|
||||
|
||||
### static 变量捕获
|
||||
### 2. static 变量捕获
|
||||
|
||||
```objective-c
|
||||
auto NSInteger age = 27;
|
||||
@@ -354,7 +354,7 @@ printInfoBlock();
|
||||
|
||||
|
||||
|
||||
### 全局变量捕获
|
||||
### 3. 全局变量捕获
|
||||
|
||||
```objective-c
|
||||
NSInteger age = 27;
|
||||
@@ -405,7 +405,7 @@ QA:为什么局部变量存在捕获,全局变量不需要捕获?
|
||||
|
||||
|
||||
|
||||
### 变量捕获总结
|
||||
### 4. 变量捕获总结
|
||||
|
||||
block 截获变量可以分为:
|
||||
|
||||
@@ -576,9 +576,9 @@ static void __Person__testBlockCapture_block_func_0(struct __Person__testBlockCa
|
||||
|
||||
|
||||
|
||||
## block 类型
|
||||
## 三、block 类型
|
||||
|
||||
### 类型划分
|
||||
### 1. 类型划分
|
||||
|
||||
我们知道 block 可以看成是一个 oc 对象,所以它有类型,写个 Demo1 验证下
|
||||
|
||||
@@ -619,7 +619,7 @@ block 的类型可以通过 isa 或者 class 方法查看,最终都是继承
|
||||
|
||||
|
||||
|
||||
### 如何判断 block 属于什么类型
|
||||
### 2. 如何判断 block 属于什么类型
|
||||
|
||||
Demo:
|
||||
|
||||
@@ -651,7 +651,7 @@ Demo 也同时发现,当对 `__NSGlobalBlock__` 调用 copy ,不会变为 `
|
||||
|
||||
|
||||
|
||||
### 总结
|
||||
### 3. 总结
|
||||
|
||||
| block 类型 | 环境 |
|
||||
| ------------------- | ------------------------------ |
|
||||
@@ -691,9 +691,9 @@ int main(int argc, const char * argv[]) {
|
||||
|
||||
|
||||
|
||||
## 内存管理
|
||||
## 四、内存管理
|
||||
|
||||
### ARC 针对 block 的优化
|
||||
### 1. ARC 针对 block 的优化
|
||||
|
||||
#### 1. block 作为函数返回值,并且捕获了 auto 变量
|
||||
|
||||
@@ -905,7 +905,7 @@ static struct __main_block_desc_0 {
|
||||
|
||||
|
||||
|
||||
### block 的 copy、dispose
|
||||
### 2. block 的 copy、dispose
|
||||
|
||||
```c++
|
||||
static struct __main_block_desc_0 {
|
||||
@@ -1004,9 +1004,319 @@ NSLog(@"-touchesBegan:withEvent:");
|
||||
2024-08-13 20:59:54.313367+0800 BlockExplore[29889:968688] (null)
|
||||
```
|
||||
|
||||
### block 如何修改变量
|
||||
### 3. _Block_release 和 _Block_copy
|
||||
|
||||
#### __block 修饰基本数据类型
|
||||
Demo1
|
||||
|
||||
```objective-c
|
||||
void test(void) {
|
||||
|
||||
int a = 0;
|
||||
void(^__weak weakBlock)(void) = nil;
|
||||
{
|
||||
int b = 2;
|
||||
void(^ __weak weakInnerBlock)(void) = ^{
|
||||
NSLog(@"%d", b);
|
||||
};
|
||||
a = b;
|
||||
weakBlock = weakInnerBlock;
|
||||
}
|
||||
weakBlock();
|
||||
}
|
||||
```
|
||||
|
||||
- 定义了一个局部变量 weakBlock
|
||||
|
||||
- 在 `{}` 内定义了一个栈上的 block **__NSStackBlock__** ,block 内访问了定义在 block 外部的 b
|
||||
|
||||
- 将 weakInnerBlock 赋值给 weakBlock
|
||||
|
||||
- 出了 `{}`,也意味着栈上的 block 作用域结束了。block 会调用 block_release 方法
|
||||
|
||||
- 由于出了局部作用域,栈上的 block 被释放了。所以在 `{}` 外调用 weakBlock 则存在野指针风险。编译器也会报警告:`Assigning block literal to a weak variable; object will be released after assignment` 。但可能不崩溃:栈内存虽回收但未被新数据覆盖
|
||||
|
||||
|
||||
|
||||
我们来看看 **block_release** 源码
|
||||
|
||||
```c++
|
||||
void
|
||||
_Block_release(const void *src)
|
||||
{
|
||||
struct StackBlockClass *self = (struct StackBlockClass *)src;
|
||||
extern const void _NSConcreteStackBlock;
|
||||
|
||||
if (self->isa == &_NSConcreteStackBlock // 必须是栈块类型
|
||||
// A Global block doesn't need to be released
|
||||
&& self->flags & BLOCK_HAS_DESCRIPTOR // 必须有描述符结构
|
||||
// Should always be true...
|
||||
&& self->reserved > 0) // 引用计数大于0
|
||||
// If false, then it's not allocated on the heap, we won't release auto memory !
|
||||
{
|
||||
self->reserved--;
|
||||
if (self->reserved == 0)
|
||||
{
|
||||
if (self->flags & BLOCK_HAS_COPY_DISPOSE)
|
||||
self->descriptor->dispose_helper(self);
|
||||
free(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `StackBlockClass` 表示栈 block 结构,包含:
|
||||
- isa:指向块类型的指针(栈块为 `__NSConcreteStackBlock`)
|
||||
- flag: 标志位( BLOCK_HAS_DESCRIPTOR 表示有描述符)
|
||||
- reserved:引用计数器
|
||||
- descriptor:包含析构函数 dispose_helper。当 block 捕获了 OC、C++ 对象后,编译器会自动设置此标志
|
||||
- 释放条件:
|
||||
- 排除全局 block 和堆 block:全局block `__NSConcreteGlobalBlock` 和堆上的 block 无需释放。
|
||||
- **栈块**:
|
||||
存储在栈内存中,生命周期与函数作用域绑定。
|
||||
**需要手动管理**:通过 `_Block_copy()` 复制到堆时,需用 `_Block_release()` 平衡引用计数。
|
||||
- **堆块**:
|
||||
由 `_Block_copy()` 动态分配在堆内存,受 ARC 管理。
|
||||
**自动管理**:ARC 会自动插入 `objc_release()` 调用,使用标准 Objective-C 对象释放机制。
|
||||
- 有效性检验:确保块结构完整且引用计数有效
|
||||
- 引用计数管理:
|
||||
- `self->reserved--` 引用计数减 1
|
||||
- 引用计数归0的时候,调用 dispose_helper 方法。释放 block 捕获的对象、内存
|
||||
- 释放 block 结构体内存
|
||||
- 一般来说 block 由栈管理,但是被 copy、strong 等强引用作为属性或者参数,则会调用 **_Block_copy** 拷贝到堆上。本函数管理堆化后栈 block 的引用计数
|
||||
|
||||
|
||||
|
||||
Demo2
|
||||
|
||||
```c++
|
||||
void test2(void) {
|
||||
|
||||
int a = 0;
|
||||
void(^__weak weakBlock)(void) = nil;
|
||||
{
|
||||
int b = 2;
|
||||
// 栈 block 赋值给一个 strong或copy 修饰的强引用变量,则会调用 _Block_copy 方法拷贝到堆上,变成堆 block
|
||||
void(^ __strong strongInnerBlock)(void) = ^{
|
||||
NSLog(@"%d", b);
|
||||
};
|
||||
a = b;
|
||||
// 结构体赋值,也就是此时 a 是一个堆上的 block
|
||||
weakBlock = strongInnerBlock;
|
||||
} // 离开作用域,堆上的 block 会自动调用 _Block_release 方法,内部会 free 掉,此时再去调用释放的内存,系统机制会为已经释放的内存填充 0xDEADBEEF 标记,MMU 也会触发缺页异常,发送 crash
|
||||
weakBlock();
|
||||
}
|
||||
```
|
||||
|
||||
现象:上面的代码会稳定 crash。报错:`Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)`
|
||||
|
||||
分析:
|
||||
|
||||
- 在 `{}` 内定义初始化了一个栈 block
|
||||
- 赋值给一个由强指针指向的 strongInnerBlock 变量时,自动触发 `_Block_copy` 方法,复制到堆上,变为堆 block **__NSConcreteMallocBlock**
|
||||
- 然后通过结构体赋值的方式,赋值给 `weakBlock`
|
||||
- 要出 `{}` 作用域时,则会调用 `_block_release` 方法。堆块的引用计数清零。释放后,堆内存表记为不可访问
|
||||
- 此时调用 `weakBlock()` 则会 crash。访问已经释放的堆内存。系统在释放的堆内存上添加保护(如 `EXC_BAD_ACCESS`)
|
||||
|
||||
test 和 test2 的本质区别
|
||||
|
||||
| 函数 | 内存类型 | 内存回收机制 | 崩溃原因 |
|
||||
| :------ | :------- | :---------------------- | :--------------- |
|
||||
| `test` | 栈内存 | 系统自动回收(无保护) | 内存可能未被覆盖 |
|
||||
| `test2` | 堆内存 | `free()` + 内存保护标记 | 强制触发访问异常 |
|
||||
|
||||
|
||||
|
||||
Demo3
|
||||
|
||||
<img src="./../assets/VisitReleasedStackBlockWillCrash.png" style="zoom:30%" />
|
||||
|
||||
为什么上面的代码会 crash?
|
||||
|
||||
- weakBlock 是一个栈 block(__NSConcreteStackBlock)
|
||||
- GCD 代码以 block 的形式将延迟任务添加到主队列中
|
||||
- `dispatch_block_t` 内部捕获了 block,但捕获的是原始指针,未复制 block
|
||||
- GCD 不会阻塞,函数会结束,同时栈 block 会被回收,栈帧销毁。此时 `dispatch_block_t` 内持有其悬挂指针。
|
||||
- 3s 后开始执行 dispatch_block_t 所指向的 weakBlock
|
||||
- 发现 weakBlock 所指向的栈内存被回收,调用无效指针导致 **EXC_BAD_ACCESS** 崩溃(访问野指针)。
|
||||
|
||||
|
||||
|
||||
解决方案:
|
||||
|
||||
1. 将栈 block 改为堆 block。block 修饰改为 strong
|
||||
|
||||
````objective-c
|
||||
void test3(void) {
|
||||
int a = 10;
|
||||
void(^__strong block)(void) = ^ {
|
||||
NSLog(@"%d", a);
|
||||
};
|
||||
|
||||
dispatch_block_t dispatch_block = ^{
|
||||
block();
|
||||
};
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_block);
|
||||
}
|
||||
````
|
||||
|
||||
2. 复制 block 到堆
|
||||
|
||||
```objective-c
|
||||
void test3(void) {
|
||||
int a = 10;
|
||||
void(^__weak weakBlock)(void) = ^ {
|
||||
NSLog(@"%d", a);
|
||||
};
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), [weakBlock copy]);
|
||||
}
|
||||
```
|
||||
|
||||
3. 栈帧不要回收。开启一个 RunLoop 晚退出3秒
|
||||
|
||||
```objective-c
|
||||
void test3(void) {
|
||||
int a = 10;
|
||||
void(^__weak weakBlock)(void) = ^ {
|
||||
NSLog(@"%d", a);
|
||||
};
|
||||
|
||||
dispatch_block_t dispatch_block = ^{
|
||||
weakBlock();
|
||||
};
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_block);
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
Demo4
|
||||
|
||||
<img src="./../assets/BlockAssignIsValueAssign.png" style="zoom:30%" />
|
||||
|
||||
分析:
|
||||
|
||||
- block 本质就是结构体
|
||||
|
||||
- `void (^__strong strongBlock)(void) = weakBlock;` 看上去左侧是一个 `__strong` 修饰,其实就是结构体,所以不会像对象一样,底层调用 setter 方法。所以 block 的赋值本就是按照结构体的成员变量一个个字段简单赋值而已。
|
||||
|
||||
```c++
|
||||
struct __block_impl {
|
||||
void *isa; // 指向 Block 类 (栈上)
|
||||
int flags; // 标志位 (栈上)
|
||||
int reserved; // 保留字段 (栈上)
|
||||
void (*invoke)(void); // 函数指针 (代码段)
|
||||
struct __block_descriptor *descriptor; // 描述符指针 (栈上)
|
||||
};
|
||||
|
||||
struct __block_descriptor {
|
||||
unsigned long reserved; // 保留字段 (栈上)
|
||||
unsigned long size; // Block 大小 (栈上)
|
||||
void (*copy)(void); // copy 辅助函数 (代码段)
|
||||
void (*dispose)(void); // dispose 辅助函数 (代码段)
|
||||
};
|
||||
```
|
||||
|
||||
- 当把结构体 block 的 invoke 设为 nil,由于是简单赋值,所以原来的 weakblock 的 invoke 也为 nil
|
||||
|
||||
- 所以 strongblock 的 invoke 也为 nil,所以执行 block 底层就是先判断 block 的 isa、然后根据 invoke 指针,找到代码段的函数去执行。此时已经为 nil,再去执行就会 crash
|
||||
|
||||
解决方案:将 weakblock copy 一下,即 `void (^__strong strongBlock)(void) = [weakBlock copy];`
|
||||
|
||||
|
||||
|
||||
### 4. 栈内存、堆内存保护机制
|
||||
|
||||
通过上面 test 和 test2 知道,栈内存、堆内存在释放后继续访问存在不同的表现
|
||||
|
||||
#### 1. 栈内存
|
||||
|
||||
##### 1. 栈内存的本质上是:
|
||||
|
||||
- **线性结构**:栈是连续的内存区域,通过栈指针(SP)管理
|
||||
- **自动回收**:函数退出时,只需移动栈指针即可"释放"内存
|
||||
- **物理内存不变**:移动栈指针不会立即清除数据,原内存内容保持不变
|
||||
|
||||
##### 2. 访问已释放的内存为何可能不崩溃?
|
||||
|
||||
- 无硬件保护:CPU 和 MMU(内存管理单元)不跟踪栈帧生命周期
|
||||
- 内存数据保留:除非被新的栈帧覆盖,否则数据仍可被读取
|
||||
|
||||
##### 3. 不崩溃的深层原因:
|
||||
|
||||
- 无页表标记:栈内存页始终标记为:可读写 (PROT_READ|PORT_WRITE)
|
||||
- 无隔离机制:不同函数的栈帧共享同一内存页
|
||||
- 延迟覆盖:新栈帧写入前,元数据保持有效
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### 2. 堆内存
|
||||
|
||||
##### 1. 堆内存管理的核心机制:
|
||||
|
||||
- malloc -> 向内核申请内存页 -> 设置页表属性 -> 分配内存块
|
||||
- free -> 标记内存页属性 -> 加入空闲链表 -> 触发内存页回收
|
||||
|
||||
##### 2. free 后的关键操作
|
||||
|
||||
内存标记
|
||||
|
||||
````objective-c
|
||||
// 典型内存分配器实现
|
||||
void free(void *ptr) {
|
||||
// 1. 在内存块头部写入魔数(如0xDEADBEEF)
|
||||
*(uint32_t*)(ptr - 4) = 0xDEADBEEF;
|
||||
|
||||
// 2. 通过 mprotect 设置内存页不可访问
|
||||
mprotect(ALIGN_PAGE(ptr), PAGE_SIZE, PROT_NONE);
|
||||
}
|
||||
````
|
||||
|
||||
页表更新
|
||||
|
||||
| 状态 | 页表项标志位 | 访问后果 |
|
||||
| :--- | :-------------- | :----------- |
|
||||
| 正常 | PRESENT+RW+USER | 允许访问 |
|
||||
| 释放 | ~PRESENT | 触发缺页异常 |
|
||||
|
||||
稳定崩溃的硬件基础:
|
||||
|
||||
MMU 介入:当访问 `PROT_NONE` 内存页时:
|
||||
|
||||
1. 触发缺页一场(Page Fault)
|
||||
2. 内核检查异常地址
|
||||
3. 发送 `SIGSEGV` 信号
|
||||
4. 进程终止(崩溃)
|
||||
|
||||
#### 3. iOS/Macos 优化
|
||||
|
||||
##### 1. 堆内存保护优化
|
||||
|
||||
- Malloc Scribble:释放后填充`0x55`(调试模式默认启用)
|
||||
|
||||
- Zone-based Protection:
|
||||
|
||||
```objective-c
|
||||
malloc_zone_t *zone = malloc_create_zone(0, 0);
|
||||
malloc_set_zone_name(zone, "Protected Zone");
|
||||
malloc_zone_protect(zone, 1); // 启用保护
|
||||
```
|
||||
|
||||
##### 2. 栈内存的刻意放松
|
||||
|
||||
- **性能优先**:避免栈操作时的权限检查
|
||||
- **安全边界**:仅防止栈溢出,不保护栈帧间访问
|
||||
|
||||
|
||||
|
||||
### 5. block 如何修改变量
|
||||
|
||||
#### 1. __block 修饰基本数据类型
|
||||
|
||||
```objectivec
|
||||
typedef void(^MyBlock)(void);
|
||||
@@ -1178,7 +1488,7 @@ __attribute__((__blocks__(byref))) __Block_byref_num2_0 num2 = {(void*)0,(__Bloc
|
||||
|
||||
|
||||
|
||||
#### __block 修饰对象
|
||||
#### 2. __block 修饰对象
|
||||
|
||||
对` __block` 修饰的对象,clang 转换为 c++ 后如下:
|
||||
|
||||
@@ -1307,13 +1617,13 @@ block 内部对变量的值修改其实就是对 block 内部自定义结构体
|
||||
|
||||
|
||||
|
||||
#### 什么情况下需要 __block
|
||||
#### 3. 什么情况下需要 __block
|
||||
|
||||
局部变量:基本数据类型、对象数据类型
|
||||
|
||||
|
||||
|
||||
#### 什么情况下不需要 __block
|
||||
#### 4. 什么情况下不需要 __block
|
||||
|
||||
- 全局变量(不截获)
|
||||
- 静态全局变量(不截获)
|
||||
@@ -1323,7 +1633,7 @@ block 内部对变量的值修改其实就是对 block 内部自定义结构体
|
||||
|
||||
|
||||
|
||||
## `__forwarding` 的设计
|
||||
## 五、`__forwarding` 的设计
|
||||
|
||||
Demo1
|
||||
|
||||
@@ -1587,7 +1897,7 @@ Tips:实现 block 拷贝及其捕获对象的函数是 `_Block_copy`,工作
|
||||
|
||||
|
||||
|
||||
## Block 内存引用
|
||||
## 六、Block 内存引用
|
||||
|
||||
对于` __block` 修饰的变量进行研究
|
||||
|
||||
@@ -1803,7 +2113,7 @@ __attribute__((__blocks__(byref))) __Block_byref_p_0 p = {
|
||||
|
||||
|
||||
|
||||
## 循环引用
|
||||
## 七. 循环引用
|
||||
|
||||
self 是一个局部变量,block 访问 self,即存在捕获变量的效果。
|
||||
|
||||
@@ -1817,7 +2127,7 @@ __attribute__((__blocks__(byref))) __Block_byref_p_0 p = {
|
||||
|
||||
可以看到 Person 对象的 dealloc 方法没有执行,里面的打印信息没有输出。
|
||||
|
||||
### ARC 下
|
||||
### 1. ARC 下
|
||||
|
||||
`__weak`、`__unsafe_unretained` 修饰 `__block` 所修饰的变量。区别在于:
|
||||
|
||||
@@ -1880,7 +2190,7 @@ p.block();
|
||||
|
||||
|
||||
|
||||
### MRC 下
|
||||
### 2. MRC 下
|
||||
|
||||
方法1: `__unsafe_retained` 修饰。`__unsafe_unretained typeof(self) weakself = self;`
|
||||
|
||||
@@ -1888,7 +2198,7 @@ p.block();
|
||||
|
||||
|
||||
|
||||
## 为什么加 weakself、strongself
|
||||
## 八、为什么加 weakself、strongself
|
||||
|
||||
weakSelf 是为了使 block 不持有 self,避免 Retain Circle 循环引用。在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。
|
||||
|
||||
@@ -1922,7 +2232,9 @@ block 执行完后这个 strongSelf 会自动释放,没有不会存在循环
|
||||
});
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
|
||||
## 九、总结
|
||||
|
||||
1. block 本质是什么?封装了函数调用及其调用环境的 OC 对象。本质实现是一个结构体。
|
||||
2. `__block` 的作用是什么?可以对 block 外部的变量进行捕获,可以修改。但是需要注意内存管理相关问题。比如`__weak`、`__unsafe_unretained`、`__block`
|
||||
|
||||
Reference in New Issue
Block a user