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

原创 2016年06月01日 21:49:31

利用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,所有的“预缓存”任务的分发和执行都会自动暂定,最大程度保证滑动流畅。

版权声明:本文为博主原创文章,未经博主允许不得转载。

iOS中的谓词(NSPredicate)使用

首先,我们需要知道何谓谓词,让我们看看官方的解释: The NSPredicate class is used to define logical conditions used to cons...
  • fishmai
  • fishmai
  • 2017年03月25日 10:47
  • 243

【IOS开发】UIImage 和 NSString的保存

主要用的函数和方法上一篇中都有介绍,这里就不在重复了。如果有需要可以自行阅读前文: 这里主要讲解如何使用UIImagePickerController选择照片,显示在UIImageView中并且...

利用Windows空闲时间绘制随机大小和颜色的矩形

Windows 有很多的 “ 空闲时间 ”,在这期间所有的消息队列都是空的,Windows 就在等待键盘或者鼠标的输入。 那么能否在空闲期间从某种程度上获取控制并绘制随机矩形,而一旦有消息加载到程序的...

平移 & 旋转 & 双缓冲 & 双缓存 & 空闲调用 & 激活函数(启用功能) & 按键控制

参考: kiya-z OpenGL系列 旋转金字塔#include #include #pragma comment(linker,"/subsystem:\"windows\" /entry:...

实现一个可以在队列满时,将溢出的部分缓存入文件,待空闲时取出的队列

上代码: import java.util.Collection; import java.util.concurrent.TimeUnit; public interface ICacheQueu...

OS的核心运转过程--空闲任务的建立及运行过程

OS的核心运转过程--空闲任务的建立及运行过程 OS是如何运转起来的?(一般情况下,不考虑系统的一些配置被修改). 1.系统变量常量配置 在程序的最开头,先定义你需要的任务的堆栈大小...

uCOS3空闲任务

作者 :JCY QQ :1501614847 CSDN :JCY-JCY 来自09级安徽宿州学院电子创新实验室 此文中对uCOS3空闲的理解,若有错误之处请指出,不胜感激! 此文中所有内容仅...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:利用RunLoop空闲时间执行预缓存任务
举报原因:
原因补充:

(最多只允许输入30个字)