首页 > 代码库 > NSRunLoop的利用

NSRunLoop的利用

一、NSRunLoop的理解
 
     在初学C语言编程的时候,经常会写一种控制台中的程序,程序启动黑色的输入框,等待用户的输入,输入一组数据之后程序继续往下执行程序在等待用户输入数据时会阻塞,这是一种最简单的单线程程序执行的模式。这种简单的编程模型在实际的应用中很难满足用户的需要,比如ios中的app的主线程需要同时响应多种消息,用户触摸事件、timer定时器到时、其它线程发送过来的消息,很自然可以想到为了都能处理到这些消息,那么就在一个while循环中,循环遍历处理这些不同是事件(source),这就是runloop的雏形,也是runloop的含义。
    
     runloop的作用是不是只是简单的循环遍历处理事件呢,当然没那么简单了。首先不同的事件的优先级别是不一样的,用户触摸事件一般高于其它事件类型的,这也是ios系统体验比安卓流畅的一个原因,在一个时间段内主线程收到不同的消息时,runloop优先处理用户交互产生的事件。当然,要达到这种效果,就需要将source分成不同的类型,并把这些类型对应不同的模式(NSRunLoopMode),在某种模式下面只处理指定的几种事件,比如一个UItableView在滑动的时候,主线程的RunLoop会在NSRunloopTrackingMode模式下面运行,这个模式下面是不会处理timer消息的,这样保证在滑动的时候,runloop能一直处理滑动的消息,不停的滑动不停的刷新界面,保证界面的流畅度。
 
     可以想像出来,runloop就类似于一个消息泵,每种事件(source)到达的时候,会在对应的类型的事件队列下面排队,runloop运行在不同模式时从不同的队列中取出消息进行执行,就如下图:
     右边是不同的事件,左边是一个线程中的runloop。那么runloop在一个循环时,就会对应不同的状态,runloop开始,runloop结束,此外还可以想到runloop正在等待消息。对应到真正的类型如下:
    
 
     依次代表的含义是,进入runloop,将要处理timer消息,将要处理其它类型的消息,将要进入等待,结束等待,退出几种含义。
     
二、RunLoop的实现
     
     Runloop的源代码没有真正的源代码,只有找到替代实现cocotron,里面有Runloop的实现,看了之后对Runloop会有更多理解。Runloop是否真的是while循环实现的呢,猜测应该不是,隔一段时间遍历检测这样的效率太低了,类似于poll,需要遍历所有的事件,没有事件响应进入休眠,有事件遍历检测,这样不停休眠唤醒。其实还有更高效率的实现,类似于epoll,在内核中注册事件的回调,需要内核支持。runloop是不是这样实现的不得而知。
 
     官方文档说Runloop是每个线程独有的,可以猜测出Runloop是每个线程结构中的一个对象,只是没有进入running的状态,主线程会默认跑起来。NSTimer需要加入到RunLoop中才会生效,因为是runloop中响应timer消息,想想如果一个Runloop没有跑起来加入一个timer是不会有什么反应的。
 
三、RunLoop的利用
     
     有很多场景下面,我们需要对app的性能进行优化,比如一个滑动列表,我们需要优化FPS,那么我们需要保证在滚动的过程中,主线程不要做多余的事情,比如读取大的图片文件,比如渲染大涨图片,这些都会产生卡顿,有一种场景是,用户下载一张图片,通过通知(同步的方式)的方式告知cell下载成功进行刷新,如果table在滚动的过程中收到这个通知,那么就会对图片进行渲染,图片尺寸小点没关系,大点的图片将会产生卡顿。有没有办法在滑动停止,或者runloop空闲的时候执行某些消息呢?
     下面是我的一种实现,可以下载源代码进行阅读,通过performSelector的方式,将要执行的selector转化成invocation放入队列中,同时检测runloop的状态,空闲的时候再进行执行。
     实际上系统已经考虑到这个场景了,有一种runloopmode是NSConnectionReplyMode,指的是在收到网络回包通知时在这种mode下面响应,而现在大量使用第三方类库的时候没有考虑这个机制。
 
四、RunLoop带来的坏处
     
     我们知道performSelector是在Runloop中响应的,在一轮循环中执行某个selector,这个时候如果在selector中发生了crash,那么会经常看到调用堆栈的上层只是Runloop相关的内容,很难判断出一个是哪里调用的selector。不知道是否有人对此研究,是否可以对调用层进行跟踪。
 
     

NSRunLoop的利用