IMG_5802.JPG

前言

  • RunLoop与线程是一一对应的,一个runloop对应一个核心的线程。为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局字典里。
  • Runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有任务就会被唤醒去执行任务。
  • Runloop在第一次获取时被创建,在线程结束时被销毁。
  • 对于主线程来说,runloop在程序一启动就默认创建好了。
  • 对于子线程来说,runloop是懒加载的,只有当我们使用时才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

    定义

  1. runloop底层的do while循环与系统的do while循环有差异,系统的回占用一定cpu,runloop则占用很少
  2. 保持程序的持续运行
  3. 处理APP中的各种事件(触摸、定时器、performSelector)
  4. 节省CPU资源、提升程序的性能:该做事就做事,该休息就休息

    初探

  • 建立一个NSTimer,断点,bt查看堆栈信息
  • 发现此timer是通过CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION控制,不断循环

image.png

runloop应用图

未命名文件 (3).jpg

底层

  • runloop在底层封装了CFRunLoop
  • result没有停止同时没有结束,就会一直运行,由内部条件控制

image.png

  • __CFRunLoop底层是一个结构体
    • _commonModes、_commonModeItems、modes是集合类型

image.png

线程与runloop关系

  • 线程与runloop是一一对应的,字典形式存在。

image.png

  • 当将timer添加到runloop时设定mode -> timer依赖mode去run
  • defaultMode有多个线程事务
  • 所有的任务的事件的处理依赖模型做事,做完事通过事务Items(事务)
  • items(source0 source1 timers observers)是如何通过mode在runloop中执行的?

image.png


  1. beforwaiting afterwaiting 监控耗时 ———-
  2. 超时判断 runlooprun

  3. source - timer - observer = item + mode -> run

  4. timer切入点
    1. addtimer -> mode(当前runloop) -> run
    2. runloop run - 9.1
    3. 遍历timers, -> doTimer
    4. dotime -> CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION

源码

CFRunLoopTimer

712CB59C-9339-4846-AD83-D480821D7D5A.png

  • 向commonMode中添加Item,item即source、timer、observer源

image.png

  • CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName)
  • 这里只有将timer加入到集合中,还未执行,需要执行CFRunLoopRun

未命名文件 (8).jpg

runloopRun图

image.png
image.png

  • runloop状态
    • 可以通过不同状态监听runloop
    • kCFRunLoopBeforeWaiting、kCFRunLoopAfterWaiting使用最广泛
    • 通过监听kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting监听做事情的一个run流程(所需时间),对应上述runloopRun图的第一个和第十个步骤

IMG_9D4539721D08-1.jpeg

  • runloop虽然是do while循环,但也有定时器(GCD source),判断是否超时

1631589029792.jpg

runloop应用

界面优化

卡顿

  • 原理 -> 屏幕(cpu计算、gpu渲染、framebuffer帧缓冲区、video Controller控制器、monitor显示)
  • iphone实现双缓冲区,来回切换,解决耗时问题。当某一缓冲区渲染不及时,进行丢掉,出现卡段,界面不流程现象。

未命名文件 (14).jpg

  • vsync 垂直信号,出现垂直信号代表要画面

未命名文件 (14).jpg

监听卡顿

  • YYKit -> YYFPSLabel检测,CADisPlayLink绑定在VSync上,检测时间间隔,计算出fps -> 60 -> 16.67ms

image.png

  • BlockMonitor -> Runloop检测卡顿
    • 注册一个CFRunloopObserverRef通知,优先级最小,添加到主Runloop中,监测monitor的dispatch_semaphore_t
    • 在子线程监控时长,超时时间是1秒,没等到信号量,st不等于0

image.png
image.png

  • 微信卡顿检测 -> matrix
    • 不止检测卡顿,还包含了堆栈信息
    • 开启runloop监听,开启只线程
  • 滴滴卡顿检测 -> 不断检测主线程信号,主线程保活,类似socket的心跳机制,监听两次信号时间间隔,超过范围即卡顿,5s

    • while 循环中标记主线程,在主线程调用方法

      优化

      方案1 -> 预排版、预存储
  • 模型数据提前写好,请求网络 -> model 数据(json + frame height 富文本),我们可以将model提炼出来

  • 子线程去计算处理好这些事务,再回到主线程reload

image.png
方案2 -> 预解码、预渲染

  • UIimage本质是一个模型
  • SDWebImage拿到二进制流,进行decode解码成image Buffer,再添加到Frame Buffer中,提供渲染准备
  • 因此预解码将decode放在子线程处理,完成后回调到主线程
  • AVFoundation是苹果底层处理,FFMpeg主要就是编解码处理很牛

音视频设备模块框架图 (6).jpg
方案3 -> 按需加载
方案4 -> 异步渲染

  • UIView和Layer的关系
    • UIView作用:渲染、交互
  • 渲染 -> 事务
    • layout 构建视图
    • displayer 绘制
    • prepare coreAnimation相关工作
    • commit 提交事务 -> render
    • 举例 -> drawRect,drawRect依赖于UIView(Rendering),context -> 绘制 -> 准备animation -> 渲染
      • 设置代理,自定义构建视图、绘制等方法,将一些操作放在子线程,完毕后回调回主线程

音视频设备模块框架图 (7).jpg

  • 第三方框架Graver
    • 生成一张位图
    • Graver继承了UIView,还是具备点击等交互功能

image.png

5种mode

  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
  4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

    线程保活

    bugly原理

  • 通过监听Runloop的两次source -> KCFRunloopBeforeSources和kCFRunLoopAfterWaiting的监控,创建信号量的方式渲染界面频率来监控帧率
  • 崩溃种类
    • single异常,注册信号量来捕获
    • OC异常,NSException,注册NSUncaughtExceptionHandle