详解NSRunLoop

NSRunLoop似乎已经和GCD,Runtime并列成为面试必问的三个问题之一了。 但奇怪的是,GCD和Runtime我们都能找到不少相关的代码,唯有NSRunLoop用在实际项目中的地方极少。导致我们只能看极度抽象的概念,所以这里,我不打算谈已经有很多文章讲过的NSRunloop原理,我们把注意力放在,如何在实际项目中使用NSRunloop代码。接下来,我会假设你已经熟稔RunLoop的一些基础知识。

如何使用NSRunLoop优化大图加载的问题

如何观察主线程的Runloop的运行状态

首先,我们需要明确的是,每次一个Runloop循环的时候都是有不同状态的。那么,有哪些状态呢?”



大概有上述状态。 CoreFoundation中也有对应的观察Runloop状态的方法。 这个东西叫做CFRunLoopObserverRef,它是一个观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

那么如何创建一个CFRunLoopObserverRef,对应的方法是这个。CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);

调用这个方法需要六个参数。 第一个参数用于分配observer对象的内存 第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行 第四个参数用于设置该observer的优先级 第五个参数用于设置该observer的回调函数 第六个参数用于设置该observer的运行环境

其中比较重要的是第二个参数和第五个参数。 第二个参数决定了你观测的Runloop状态的种类,第五个参数决定了当你的观察者观测到Runloop运行到某一个状态时,你应该做哪些操作。

那么说了这么多,我们又怎么利用优化大图加载呢? 假如我们现在需要实现这么一个界面


这个tableView中一共有15张图片,每张图片的分辨率是2034 × 1525,也就是说,如果我们不加任何处理,一次Runloop循环我们需要在屏幕绘制15张大图。一般来说,性能不好的iOS设备肯定会滑动的非常不流畅。 那么我们换个思路,既然一次渲染15张图片比较吃力,那么我们把15张图片分15次渲染不就可以了么。也就是,把原来一次runloop完成的操作,放在了15次runloop里完成。

怎么做到这样? 就是利用我们上面讲述的CFRunLoopObserverRef。 想象一下,上图的UITableViewCell中有3个UIImageView,造成卡顿的问题无非是当cell.imageView.image = image的时候。因为这时候主线程才会把图片绘制到imageView上。

所以,优化的流程就变成了这样。 

所以,第一步,我们先为主线程的Runloop设定一个观察者。”



我们注册的activity类型是kCFRunLoopBeforeWaiting,观察每次runloop即将进入睡眠的时候。 然后Yes的意思是指,每次主线程runloop进行了一次循环的时候都会被观察到。 callback是什么呢?




这个地方看的似乎有点难懂。 那么我们先不急着看回调方法。 我们先看看是如何处理TableView的datasource方法的。






1.每次进入cellForIndex方法的时候首先会执行task_5这个方法。这个方法的内容如下。




其实就是清空这个Cell里的所有子控件。
然后执行了task_1这个方法。



主要是负责添加cell中的uilabel。 由于上述两个任务都是耗时比较少的任务,所以,并没有动用我们的runloop去做特殊处理。

1.接下来的task_2,task_3,task_4都动用了我们的Runloop来做特殊处理。这些方法都是干什么的呢?



他们三个的功能类似,就是为我们的cell添加一个imageView并且赋值。 这也是耗时最多的地方。所以,我们把他们分派到多次runloop中分散执行。



“我们的DWURunLoopWorkDistribution中,有一个属性,叫做tasks,是用来专门储存我们的任务的,每一个任务都是一个block。存储用户希望我们执行的操作。 所以,看到这里,回调方法里执行的操作应该不难理解了。


判断我们的tasks的数量是否为0,为0表明没有需要添加的imageView。

当tasks的数量大于0的时候,拿出一个任务,一个任务就是一个block也就是某个添加imageView的任务,这时候执行它即可。

由于一次runloop的某个activity只会出现一次,也就意味着这个callback方法也只会在一次runloop中执行一次。 所以,我们就保证了一次runloop只添加一个imageView。 实现了优化的作用。

猜你喜欢

转载自blog.csdn.net/box_kun/article/details/79589812
今日推荐