简单聊聊iOS中的Runloop
对于偏概念上的内容,一直都让人模棱两可,摸不着头脑。现在在这里整理下相关的概念,梳理下iOS中涉及到Runloop相关的内容。
Runloop是什么
对于很多刚刚接触到Runloop的iOS开发者来说,或许会对Runloop是什么感到好奇。网上也可以找到很多相关的概念,这里不再赘述,但却总是很难找到一个统一的说法。个人对iOS中Runloop的理解,应该称之为运行循环或者说运行闭环,原则上来说是一种消息循环机制,本质上简单来说,就是在线程中运行的事件监听触发机制循环。从这个概念来说,我们就可以知道,Runloop依赖于线程存在,同时保证程序的运行。所以我们的APP就需要主线程有一个Runloop来维持。
从这个角度来看,似乎Runloop和线程是相互依赖的,其实不然,两者是属于一种一一对应的关系,类似于字典,但是线程却并不与Runloop有依赖关系。
Runloop的本质
好多人喜欢将Runloop本质类比为do-while循环,这样也可以更简单的理解其原理,这里也不赘述。但是在其实质上,Runloop的本质更加复杂一些,涉及到Mach层内核及mach_msg() 函数等相关内容,同时有几点我们需要特别注意一下:
- Runloop是一种系统机制,我们在APP的开发中并不会创建或初始化以及明确的管理任何一个Runloop
- APP的每一个线程中,包括主线程,都有与之对应的唯一的Runloop,根据其需要自动创建,当我们需要用到时,调用NSRunloop的currentRunLoop即可(线程中的Runloop是懒加载的,只有主线程的Runloop是默认启动的)
- 从Runloop的角度来说,NSTimer并非是一种“输入“,我们可以将其理解为一种触发事件
- 通常来说,NSRunloop并非是线程安全的,所以不可在不同的线程间调用NSRunloop相关的方法;但是对于Core Foundation下CFRunLoopRef,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的
两套API
我们在iOS的开发中,有两套可调用的API来访问Runloop
- Foundation下的NSRunloop
也就是NSRunloop,一帮情况下,NSRunloop已经可以满足我们开发中的需要 - Core Foundation下的CFRunLoopRef
NSRunloop是对CFRunLoopRef的OC封装,所以我们常用的NSRunloop相关方法,其实都有CFRunLoopRef函数与之对应
iOS中Runloop的结构
iOS中,Runloop的结构可以这样理解
Runloop->包含若干个Mode
Mode->包含若干个(Source/Timer/Observer)
对Mode的简单理解
本来此处想阐述一下关于Mode的相关内容,但是具体到Core Foundation及Foundation中的相关Mode,又太过繁复;本篇旨在简单了解开发中和面试中遇到的相关问题及概念,所以不再详细阐述每个Mode的区别,如想具体了解及学习Runloop中的Mode及其他知识,建议查阅ibireme大神的博客:
深入理解RunLoop
也可以自行下载CoreFoundation开源源码进行查看:
https://opensource.apple.com/tarballs/CF/
虽然不过多对Mode进行阐述,不过我们可以对其相关概念进行简单理解,我们先来看Foundation中Mode定义:
typedef NSString * NSRunLoopMode NS_EXTENSIBLE_STRING_ENUM;
然后是Core Foundation中关于Mode的定义:
typedef CFStringRef CFRunLoopMode CF_EXTENSIBLE_STRING_ENUM;
由此定义,本人习惯将Mode理解为一种标记,也就是说,Runloop在某个Mode下,需要触发(Source/Timer/Observer)时,会去找对应的有此标记的(Source/Timer/Observer)去执行。当然,此种表述并不准确,实际上也要复杂的多,仅为初步理解Mode概念即可。
两种source
在Runloop运行中的事件源,分为两种:
Source0:非基于Port的用于用户主动触发的事件
Source1:基于Port的通过内核和其他线程通信事件
关于两种Source的理解,可以简单理解为:Source0是与用户交互相关的事件源,而Source1是与系统相关的消息事件源
Timer不准原理
关于Timer,我们通常说不建议使用NSTimer作为计时器,主要原因有以下两点:
- ARC下的循环引用问题
- 计时器不准的问题
具体的解决方案及替换方案,网上有很多相关帖子进行说明(比如GCD的Timer等),此处也不再赘述。
关于第一点循环引用的问题,其实本质上是因为调用对象与Timer的双向强引用,以及将Timer添加到Runloop中后,共同作用的结果,具体可查阅相关帖子,此处不赘述,或以后可另开一篇博客,详细阐述此内容;
至于第二点,Timer不准的原因有两个,一个是因为,Runloop的每一个Mode的触发机制不同,第二个是因为Timer所在的当前Runloop,可能会同时有许多其他触发事件等,均可能导致Timer的不准。
Runloop 的常用实际应用
Runloop的实际应用,除了上述提到过的Timer,本人认为可能会比较常接触到的场景,应该是常驻线程:
具体做法,就是在创建线程后,将当前线程的currentRunLoop加入Mode并run即可。
另外,Runloop也可以做一些关于tableView加载cell时的耗时卡顿问题优化,主要是通过Runloop监听等,将耗时操作分散开。以后会另开一篇帖子,单独对tableView的优化进行讨论时具体分析。
其他注意事项
关于主线程的Runloop与AutoreleasePool之间的关系,有一点应该有所了解:主线程的Runloop开始的时候创建AutoreleasePool,在Runloop结束的时候,销毁AutoreleasePool;基于此,对于我们自己在管理线程等开发过程中,也有很大帮助。