mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
docs: 汇编研究
This commit is contained in:
@@ -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,函数调用堆栈将不会释放,造成泄漏
|
||||
|
||||
- 虽然可以保活,但是会增加业务异常风险,所以需要衡量
|
||||
|
||||
|
||||
|
||||
### 线程保活
|
||||
|
||||
应用场景:经常在子线程中处理某些逻辑的场景。如果销毁再创建再销毁再创建效率很低,这个情况下就需要线程保活。
|
||||
|
||||
Reference in New Issue
Block a user