美文网首页iOS知识点
iOS RunLoop进阶之一----CFRunLoop延迟加载

iOS RunLoop进阶之一----CFRunLoop延迟加载

作者: 游龙飞雪 | 来源:发表于2018-03-22 12:44 被阅读176次
        对于列表,流畅性是衡量性能的一个重要指标。如果cell中要加载多张大图,比如每张图超过2~3M,那么在滑动的时候系统一边要处理滑动,一边又要渲染这么大的图片多张,肯定会卡,这是毫无疑问的。
    

    那么这种卡顿现象是怎么造成的呢?这不是异步加载的问题,通俗一点说是主线程更新UI的问题。

    我们知道每个线程都有一个RunLoop,主线程的RunLoop是系统默认开启的。而RunLoop在不处理事件的时候就会休眠。那就引出一个概念,我个人称之为 RunLoop的“工作周期”(类比控制器的生命周期),各种状态如下:

    /* Run Loop Observer Activities */
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将休眠
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
    };

    这里有个状态是 kCFRunLoopBeforeWaiting(即将休眠),我们设置一个 timer 每隔 0.01s 响应一次,并添加到 RunLoop 的默认模式中,那么时钟在列表滑动的UI事件结束之后才会有效,对应本例中就是列表滑动结束之后时钟跳动一次即将结束的时候,这个时候再让 RunLoop 来做加载图片,就不会跟滑动冲突了,也就不会造成列表卡顿了。(注意:列表滑动的时候是不加载图片的。)

    这就是本文讨论的“延迟加载图片”。

    怎么知道什么时候是 kCFRunLoopBeforeWaiting 状态呢?在CFRunLoop中,有个监听者,我们创建这个监听者通过函数回调(C语言中回调一般都是函数指针)来监听状态处理任务。
    代码如下:

    image.png

    这里理一下思路:
    背景环境:控制器中有一个tableView,有几百个cell,每个cell中有3个imageView,每个imageView要加载 3M 的大图片。
    在cellForRow的数据源方法中不直接给cell.imageView设置图片,而是利用runloop的 kCFRunLoopBeforeWaiting 状态的回调函数执行设置图片的任务。

    1. 在viewDidLoad中添加监听者监听runloop的状态;

    2. 设置时钟,添加到runloop的 “默认模式” 执行,一定要默认模式才是本文思路的关键;

    3. 设置一个属性--存放任务的数组tasks,在cellForRow中,把给imageView设置图片的代码作为任务用block包装,将这个block任务存放到tasks数组中;
      伪代码如下:


      image.png
    4. 回调函数Callback 中(回调函数是C语言的函数),从tasks数组中取出任务并执行,随即更新tasks。


      image.png

    这样,就达到了延迟执行的效果。


    需要的注意的是,这样子会造成内存疯长,处理方法是在cellForRow的数据源方法中,移除cell.contentView中的子控件,这里其实就是3个imageView,在 添加任务的block任务中,再给 contentView 动态添加 3 个 imageView,用CPU的性能换来内存空间,就不会使内存疯长了。

    相关文章

      网友评论

        本文标题:iOS RunLoop进阶之一----CFRunLoop延迟加载

        本文链接:https://www.haomeiwen.com/subject/xfioqftx.html