利用RunLoop空闲时间执行预缓存任务

最近在做高度自适应的UITableView的时候,使用了一个FDTemplateLayoutCell的开源组件。
它的主要原理是利用RunLoop空闲时间执行预缓存任务
FDTemplateLayoutCell 的高度预缓存是一个优化功能,它要求页面处于空闲状态时才执行计算,当用户正在滑动列表时显然不应该执行计算任务影响滑动体验。一般来说,这个功能要耦合 UITableView 的滑动状态才行,但这种实现十分不优雅且可能破坏外部的 delegate 结构,但好在我们还有RunLoop
这个工具,了解它的运行机制后,可以用很简单的代码实现上面的功能。

空闲RunLoopMode

当用户正在滑动 UIScrollView 时,RunLoop 将切换到 UITrackingRunLoopMode
接受滑动手势和处理滑动事件(包括减速和弹簧效果),此时,其他 Mode (除 NSRunLoopCommonModes 这个组合 Mode)下的事件将全部暂停执行,来保证滑动事件的优先处理,这也是 iOS 滑动顺畅的重要原因。当 UI 没在滑动时,默认的 Mode 是 NSDefaultRunLoopMode
(同 CF 中的 kCFRunLoopDefaultMode),同时也是 CF 中定义的 “空闲状态 Mode”。当用户啥也不点,此时也没有什么网络 IO 时,就是在这个 Mode 下。

用RunLoopObserver找准时机

注册 RunLoopObserver 可以观测当前 RunLoop 的运行状态,并在状态机切换时收到通知:

RunLoop开始
RunLoop即将处理Timer
RunLoop即将处理Source
RunLoop即将进入休眠状态
RunLoop即将从休眠状态被事件唤醒
RunLoop退出
因为“预缓存高度”的任务需要在最无感知的时刻进行,所以应该同时满足:

  • RunLoop 处于“空闲”状态 Mode
  • 当这一次 RunLoop 迭代处理完成了所有事件,马上要休眠时
    使用 CF 的带 block 版本的注册函数可以让代码更简洁:

    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFStringRef runLoopMode = kCFRunLoopDefaultMode;
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) { // TODO here});
    CFRunLoopAddObserver(runLoop, observer, runLoopMode);

    在其中的 TODO 位置,就可以开始任务的收集和分发了,当然,不能忘记适时的移除这个 observer。

    分解成多个RunLoop Source任务

    假设列表有 20 个 cell,加载后展示了前 5 个,那么开启估算后 table view 只计算了这 5 个的高度,此时剩下 15 个就是“预缓存”的任务,而我们并不希望这 15 个计算任务在同一个 RunLoop 迭代中同步执行,这样会卡顿 UI,所以应该把它们分别分解到 15 个 RunLoop 迭代中执行,这时就需要手动向 RunLoop 中添加 Source 任务(由应用发起和处理的是 Source 0 任务)Foundation 层没对 RunLoopSource 提供直接构建的 API,但是提供了一个间接的、既熟悉又陌生的 API:

    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;

    这个方法将创建一个 Source 0 任务,分发到指定线程的 RunLoop 中,在给定的 Mode 下执行,若指定的 RunLoop 处于休眠状态,则唤醒它处理事件,简单来说就是“睡你xx,起来嗨!”于是,我们用一个可变数组装载当前所有需要“预缓存”的 index path,每个 RunLoopObserver 回调时都把第一个任务拿出来分发:

    NSMutableArray *mutableIndexPathsToBePrecached = self.fd_allIndexPathsToBePrecached.mutableCopy;
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0,
    ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {if (mutableIndexPathsToBePrecached.count == 0)
    { CFRunLoopRemoveObserver(runLoop, observer, runLoopMode); CFRelease(observer); // 注意释放,否则会造成内存泄露 return;
    }
    NSIndexPath *indexPath = mutableIndexPathsToBePrecached.firstObject;
    [mutableIndexPathsToBePrecached removeObject:indexPath];
    [self performSelector:@selector(fd_precacheIndexPathIfNeeded:)
    onThread:[NSThread mainThread]
    withObject:indexPath waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
    });

    这样,每个任务都被分配到下个“空闲” RunLoop 迭代中执行,其间但凡有滑动事件开始,Mode 切换成 UITrackingRunLoopMode,所有的“预缓存”任务的分发和执行都会自动暂定,最大程度保证滑动流畅。

利用RunLoop空闲时间执行预缓存任务相关推荐

  1. 下班做什么副业?利用下班空闲时间赚点外快!

    下班做什么副业?利用下班空闲时间赚点外快! 大城市生活压力巨大,房贷.伙食.生活费扣除后一个月工资所剩无几,但到手的工资却不见增长.有些上班族的工作比较清闲,因此下班后的时间就可以利用起来,赚点小钱. ...

  2. 学计算机空闲时间,空闲时间

    在计算机中,CPU的运行速度远远快于I/O设备的速度,当内存中仅有一道程序时,每逢该程序在运行中发出I/O请求后,CPU空闲,CPU等待I/O设备完成I/O请求的时间,称为CPU空闲时间.操作系统采用 ...

  3. 怎么样利用空闲时间做网赚?

    今天这期内容大周来给粉丝们分享一个适合新手操作的方法,不需要露脸拍摄视频,在家用手机也可以操作起来. 不用担心自己没有颜值.没有技术.没有才艺,用手机做视频剪辑,每天稳定收益200-300还是可以的. ...

  4. 利用空闲时间挣钱的兼职

    空闲时间的兼职 体力类 跑腿类 调查类 推广类 陪玩类 配音类.直播类 小时工 代购类 电商类 休闲类.听音乐.看视频 技能类 驾驶类 PPT 写作类 拆书稿 翻译类 技能平台类 程序技术类 设计类 ...

  5. 程序员如何利用空闲时间挣零花钱

    一: 私活 作为一名程序员,在上班之余,我们有大把的时间,不能浪费,这些时间其实都是可以用来挖掘自己潜在的创造力,今天要讨论的话题就是,程序员如何利用空余时间挣零花钱?比如说周末可以赚外快啊,在网上接 ...

  6. iOS 之如何利用 RunLoop 原理去监控卡顿?

    [CSDN 编者按]简单来说APP卡顿,就是FPS达不到60帧率,丢帧现象,就会卡顿,但是很多时候,我们只知道丢帧了,具体为什么丢帧,却不是很清楚,那么我们要怎么监控呢? 作者 | 枫叶无处漂泊    ...

  7. 如何利用 RunLoop 原理去监控卡顿?

    卡顿问题,就是在主线程上无法响应用户交互的问题.如果一个 App 时不时地就给你卡一下,有时还长时间无响应,这时你还愿意继续用它吗?所以说,卡顿问题对 App 的伤害是巨大的,也是我们必须要重点解决的 ...

  8. SQL Server 执行计划缓存

    原文:SQL Server 执行计划缓存 标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储 ...

  9. java dataset redis,利用Spring-Data-Redis和Jedis操作Redis缓存

    概述          Jedis是redis官方推荐的用于访问Java客户端,在https://github.com/xetorthio/jedis下载最新的jedis. 访问redis 1.访问简 ...

最新文章

  1. JavaScript继承详解(四)
  2. 人参中第一次膜你退货
  3. iPhone4S出现应用无法打开时的解决方案
  4. 如何在UIAlertView中显示进度条
  5. live messenger与稀疏文件—Sparse File Bit
  6. java中channelmessage,MessageStore支持的QueueChannel与Spring Integration Java Config
  7. 20220209-CTF MISC-BUUCTF-伪加密(ZIP文件块 十六进制分析)
  8. 洛谷 P1091 合唱队型
  9. ISCC2014-reverse
  10. 【HDU2825】Wireless Password【AC自动机,状态压缩DP】
  11. 如何在旅途中提升 MacBook 电池电量?
  12. 厉害,96秒100亿,阿里双十一到底做了什么杠过亿级流量??
  13. Bregman 散度
  14. 数字图像处理实践(二)
  15. 100台电脑无盘服务器配置,100台网吧无盘系统配三星840PRO方案解读
  16. python 爬取网易云音乐歌单
  17. UNIX编程艺术学习笔记-1
  18. linux ftp命令大全,linux ftp命令详解
  19. Docker容器引擎
  20. 一个简单的猜数字游戏(附带关机惩罚)

热门文章

  1. 如何开发一款游戏?【游戏开发所需技能和开发流程】
  2. 什么是第三代半导体,半导体的发展历程,第三代半导体的前景
  3. 常见的无法上网故障原因和解决方法
  4. RxJava过滤操作符
  5. el-select数据回显
  6. 三年级优秀书籍推荐_三年级推荐书单
  7. ping协议(ICMP)的原理
  8. 360°全景影像建库流程
  9. 为什么eclipse打不开文件
  10. linux的音频处理软ubuntu,Ubuntu18.04下的音频录制和编辑软件Ardour及QjackCtl(jackd gui)...