docs: 汇编研究

This commit is contained in:
LiuBinPeng
2022-06-23 14:59:47 +08:00
parent 55d66cc4c5
commit f0e20eaf2e
30 changed files with 1042 additions and 347 deletions

View File

@@ -1320,7 +1320,7 @@ if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentM
- 性能优化
## NSTimer 经常会不准确,原因是什么?
### NSTimer 经常会不准确,原因是什么?
NSTimer 在创建的时候经常会指派到特定的 NSRunLoopMode 中去举个例子默认创建的NSTimer 是被添加到 NSRunLoopDefaultMode 中去,当你的页面上有 UIScrollView 或者子类的时如果被拖动了,当前 RunLoop 的 NSRunloopMode 会从 NSDefaultRunLoopMode 转变为 UITrackingRunLoopMode 。遇到这种情况你需要精确的 NSTimer 的话,在创建好 NSTimer 之后,设置 RunLoopMod 为 NSRunLoopCommonModes。
@@ -1407,19 +1407,39 @@ UITableView 在滚动的时候一个优化点之一就是 UIImageView 的显示
### 自动释放池
自动释放池什么时候创建和释放
自动释放池什么时候创建和释放
创建时间:第一次进入 RunLoop 的时候
App 启动后,主线程 RunLoop 里注册了2个 Observer其回调都是 `_wrapRunLoopWithAutoReleasePoolHandler`。
释放时间RunLoop 退出的时候
第一个 Observer 监听 RunLoop 的 `kCFRunLoopEntry` 状态(即将进入 RunLoop回调为 `_objc_autoreleasePoolPush()`,会创建自动释放池,其 order 为 `-2147483647`,优先级最高,保证创建自动释放池一定是发生在其他回调之前。
其他情况:当 RunLoop 将要休眠的时候释放,然后创建一个新的
第二个 Observer 监听 RunLoop 2个事件
**_wrapRunLoopWithAutoreleasePoolHandler** **0x1**
- 监听 `kCFRunLoopBeforeWaiting` (将要休眠),回调为 `_objc_autoreleasePoolPop()` 和 `_objc_autoreleasePoolPush()`,用来释放旧的自动释放池,创建新的自动释放池。
**_wrapRunLoopWithAutoreleasePoolHandler** **0xa0**
- 监听 `kCFRunLoopExit`(RunLoop 即将退出),回调为 `_objc_autoreleasePoolPop()`Observer 优先级为 2147483647优先级最低保证自动释放池的释放在其他所有的回调之后进行。
0x1 和 0xa0 是十六进制的数对应十进制为1和160
总结版在主线程执行的代码通常是写在事件回调、Timer 回调内的,这些回调都会被 RunLoop 自身状态相关的 AutoreleasePool 所包裹,所以会自动管理内存,开发者不需要手动创建 AutoreleasePool
### 事件响应
系统注册了 Source1基于 Mach port用来接收系统事件其回调函数为 `__IOHIDEventSystemClientQueueCallback`
当一个硬件事件(触摸/锁屏/摇晃等)发生时,首先 `IOKit.Framework` 会生成一个 `IOHIDEvent` 事件并由 SpringBoard 接收。SpringBoard 只接收按键、触摸、传感器等几种 Event然后通过 Mach Port 转发给需要处理的 App 进程。随后苹果注册的 Source1 就会触发回调,并调用 `_UIApplicationHandleEventQueue()` 进行应用内部的分发。
`_UIApplicationHandleEventQueue` 会把 `IOHIDEvent` 处理并包装成 UIEvent 进行处理和分发(其中包括 UIGesture、屏幕旋转等
### 手势识别
`_UIApplicationHandleEventQueue` 识别到一个手势时,首先会调用 cancel 将当前的 touchBegin/End/Move 系统回调打断,然后系统会将对应的 `UIGestureRecognizer ` 标记为待处理。
苹果注册了一个 Observer 监控 RunLoop 的 `kCFRunLoopBeforeWaiting`(将要休眠)状态,回调为 `_UIGestureRecognizerUpdateObserver`,其内部会获取所有刚被标记为待处理的 UIGestureRecognizer并执行对应的回调。
### UI 刷新
当界面的 Frame 改变,或者更改 UIView、CALayer 的层次时,或者调用了 UIView、CALayer 的 setNeedsLayout、setNeedsDisplay 方法后,这个 UIView、CALayer 会被标记为待处理(类比前端的 Virtual Dom Diff标记为 dirty并被提交到一个全局容器中。
苹果设计 UI 更新也是 RunLoop 的业务方,所以会注册一个 Obserger 监控 `kCFRunLoopBeforeWaiting`(将要休眠)和 `kCFRunLoopExit` (即将退出 RunLoop)状态,然后会执行 `_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()` 回调。内部会遍历所有待处理的 UIView、CALayer 以执行实际的绘制和渲染,更新 UI
### RunLoop 空闲时做一些任务
@@ -1445,6 +1465,33 @@ UITableView 在滚动的时候一个优化点之一就是 UIImageView 的显示
}
```
### Crash 防护
利用监控手段,比如 C/OC crash、Signal、Mach 异常,当监控到异常之后,正常来说会发生闪退等,体验较差。某些场景下希望 App 从异常中恢复,重新启动,这个可以利用 RunLoop 实现。
```objectivec
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (1) {
for (NSString *mode in (__bridge NSArray *)allModes) {
if ([mode isEqualToString:(NSString *)kCFRunLoopCommonModes]) {
continue;
}
CFStringRef modeRef = (__bridge CFStringRef)mode;
CFRunLoopRunInMode(modeRef, 0.1, false);
}
}
CFRelease(allModes);
```
但该方案存在一些缺点,需要衡量:
- 因为崩溃发生,将程序的控制权交给新的 RunLoop所以不会返回原来的 RunLoop函数调用堆栈将不会释放造成泄漏
- 虽然可以保活,但是会增加业务异常风险,所以需要衡量
### 线程保活
应用场景:经常在子线程中处理某些逻辑的场景。如果销毁再创建再销毁再创建效率很低,这个情况下就需要线程保活。