前言
- RunLoop与线程是一一对应的,一个runloop对应一个核心的线程。为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局字典里。
- Runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有任务就会被唤醒去执行任务。
- Runloop在第一次获取时被创建,在线程结束时被销毁。
- 对于主线程来说,runloop在程序一启动就默认创建好了。
- 对于子线程来说,runloop是懒加载的,只有当我们使用时才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。
定义
- runloop底层的do while循环与系统的do while循环有差异,系统的回占用一定cpu,runloop则占用很少
- 保持程序的持续运行
- 处理APP中的各种事件(触摸、定时器、performSelector)
- 节省CPU资源、提升程序的性能:该做事就做事,该休息就休息
初探
- 建立一个NSTimer,断点,bt查看堆栈信息
- 发现此timer是通过CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION控制,不断循环
runloop应用图
底层
- runloop在底层封装了CFRunLoop
- result没有停止同时没有结束,就会一直运行,由内部条件控制
- __CFRunLoop底层是一个结构体
- _commonModes、_commonModeItems、modes是集合类型
线程与runloop关系
- 线程与runloop是一一对应的,字典形式存在。
- 当将timer添加到runloop时设定mode -> timer依赖mode去run
- defaultMode有多个线程事务
- 所有的任务的事件的处理依赖模型做事,做完事通过事务Items(事务)
- items(source0 source1 timers observers)是如何通过mode在runloop中执行的?
- beforwaiting afterwaiting 监控耗时 ———-
超时判断 runlooprun
source - timer - observer = item + mode -> run
- timer切入点
- addtimer -> mode(当前runloop) -> run
- runloop run - 9.1
- 遍历timers, -> doTimer
- dotime -> CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
源码
CFRunLoopTimer
- 向commonMode中添加Item,item即source、timer、observer源
- CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName)
- 这里只有将timer加入到集合中,还未执行,需要执行CFRunLoopRun
runloopRun图
- runloop状态
- 可以通过不同状态监听runloop
- kCFRunLoopBeforeWaiting、kCFRunLoopAfterWaiting使用最广泛
- 通过监听kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting监听做事情的一个run流程(所需时间),对应上述runloopRun图的第一个和第十个步骤
- runloop虽然是do while循环,但也有定时器(GCD source),判断是否超时
runloop应用
界面优化
卡顿
- 原理 -> 屏幕(cpu计算、gpu渲染、framebuffer帧缓冲区、video Controller控制器、monitor显示)
- iphone实现双缓冲区,来回切换,解决耗时问题。当某一缓冲区渲染不及时,进行丢掉,出现卡段,界面不流程现象。
- vsync 垂直信号,出现垂直信号代表要画面
监听卡顿
- YYKit -> YYFPSLabel检测,CADisPlayLink绑定在VSync上,检测时间间隔,计算出fps -> 60 -> 16.67ms
- BlockMonitor -> Runloop检测卡顿
- 注册一个CFRunloopObserverRef通知,优先级最小,添加到主Runloop中,监测monitor的dispatch_semaphore_t
- 在子线程监控时长,超时时间是1秒,没等到信号量,st不等于0
- 微信卡顿检测 -> matrix
- 不止检测卡顿,还包含了堆栈信息
- 开启runloop监听,开启只线程
滴滴卡顿检测 -> 不断检测主线程信号,主线程保活,类似socket的心跳机制,监听两次信号时间间隔,超过范围即卡顿,5s
模型数据提前写好,请求网络 -> model 数据(json + frame height 富文本),我们可以将model提炼出来
- 子线程去计算处理好这些事务,再回到主线程reload
方案2 -> 预解码、预渲染
- UIimage本质是一个模型
- SDWebImage拿到二进制流,进行decode解码成image Buffer,再添加到Frame Buffer中,提供渲染准备
- 因此预解码将decode放在子线程处理,完成后回调到主线程
- AVFoundation是苹果底层处理,FFMpeg主要就是编解码处理很牛
方案3 -> 按需加载
方案4 -> 异步渲染
- UIView和Layer的关系
- UIView作用:渲染、交互
- 渲染 -> 事务
- layout 构建视图
- displayer 绘制
- prepare coreAnimation相关工作
- commit 提交事务 -> render
- 举例 -> drawRect,drawRect依赖于UIView(Rendering),context -> 绘制 -> 准备animation -> 渲染
- 设置代理,自定义构建视图、绘制等方法,将一些操作放在子线程,完毕后回调回主线程
- 第三方框架Graver
- 生成一张位图
- Graver继承了UIView,还是具备点击等交互功能
5种mode
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
线程保活
bugly原理
- 通过监听Runloop的两次source -> KCFRunloopBeforeSources和kCFRunLoopAfterWaiting的监控,创建信号量的方式渲染界面频率来监控帧率
- 崩溃种类
- single异常,注册信号量来捕获
- OC异常,NSException,注册NSUncaughtExceptionHandle