关于ios:RunLoop原理

53次阅读

共计 7118 个字符,预计需要花费 18 分钟才能阅读完成。

【原英】https://developpaper.com/ios-…

在晓得 Runloop 之前

一般来说,一个线程只能执行一个工作,执行完就会退出。当咱们须要让线程能够随时处理事件而不退出线程,咱们就要应用 Runloop
它外部是一个 do while 循环,一直地解决各种工作,保障线程可能的间断运行。

Runloop 的目标

保障线程中有工作时能够执行线程工作。当线程中没有工作时,让线程休眠,进步程序性,节俭资源。

Runloop 的作用

简述 详述
放弃程序间断运行 应用程序启动后,将启动主线程。当主线程启动时,会启动主线程对应该的 runloopRunloop 能够保障线程不会被毁坏。如果主线程没有被销毁,程序将持续运行。
解决应用程序中的各种事件 事件响应、手势辨认、界面刷新、主动开释池、NSTimer 等事件处理。
节俭 CPU 资源,进步程序性 当线程中有工作时,确保线程可能工作。当线程没有工作时,让线程休眠,进步程序性能,节俭资源。该做事的时候做事,该劳动的时候劳动。

Runloop 原理

名称 类型 所在框架 原子性
NSRunloop NSObject Fundation 线程不平安
CFRunloop struct CoreFundation 线程平安

为了更好地了解 Runloop,浏览源代码是一个不错的抉择。老司机说有了源码,runloop 就不会那么神秘了。首先,咱们通常说 runloop 有两种。一个是NSRunloop,另一个是CFRunloop。那么让我先通过源码理解一下CFRunloop。首先,咱们看一下根本的数据结构

1.CFRunloop的定义。

这里我正文了所有须要留神的参数

struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp 内核向该端口发送音讯能够唤醒 runloop
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //RunLoop 对应的线程
uint32_t _winthread;
CFMutableSetRef _commonModes; // 存储的是字符串,记录所有标记为 common 的 mode
CFMutableSetRef _commonModeItems; // 存储所有 commonMode 的 item(source、timer、observer)
CFRunLoopModeRef _currentMode; // 以后运行的 mode
CFMutableSetRef _modes; // 存储的是 CFRunLoopModeRef
struct _block_item *_blocks_head; //doblocks 的时候用到
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};

能够看出 CFRunloop 是一个有很多属性的构造体。
看一下这个构造中咱们须要留神的参数。每个 runloop 都有本人的 mode,并且有不止一种mode,mode 寄存着 runloop 要解决的事件源。事件源有 3 种,sourcetimerobserver
Runloop 有很多模式,然而在某个工夫只能有一种特定的模式,即 _currentMode。上面第二项形容的是runloop 的模式,它与 runloop 构造体(mode)相干参数中的几个模式无关:

名称 模式
_currentMode 以后的 runloop 模式
_modes 以后运行中的所有模式

另外,runloop 中的一种模式是 NSRunloop 罕用模式。这种模式没有意义。它只是标记模式的汇合;

名称 模式
_commonModes 指在NSRunloopCommonmodes 模式下保留的模式。咱们还能够在这个汇合中增加自定义模式
_commonModeItems 示意增加到 NSRunloopCommonmodes 的源 / 定时器;

2.CFRunloop的源代码。

struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //mode 名称
Boolean _stopped; //mode 是否被终止
char _padding[3];
// ------------->
// 几种事件
CFMutableSetRef _sources0; //sources0
CFMutableSetRef _sources1; //sources1
CFMutableArrayRef _observers; // 告诉
CFMutableArrayRef _timers; // 定时器
CFMutableDictionaryRef _portToV1SourceMap; // 字典 key 是 mach_port_t,value 是 CFRunLoopSourceRef
// <-------------
__CFPortSet _portSet; // 保留所有须要监听的 port,比方_wakeUpPort,_timerPort 都保留在这个数组中
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};

其实 CFRunloop 相干的几个定义都是构造体,CFRunloopMode也是构造体。看代码理解 CFRunloopMode 的几个相干参数,次要是下面标注的四个参数

如上所述,runloop 用于处理事件。它次要解决三种类型的事件:源 source定时器 timer 观察者 observer。那么 source 也能够分为 source0source1两种。CFRunloopMode的定义中有四个汇合,别离代表存储这四个事件源的汇合,如上所述。

runloop 中的模式次要有以下几种:

名称 形容
KCFRunloopDefaultMode app 的默认模式。通常,主线程以这种模式运行。
UitrackingRunloopMode 界面跟踪模式,用于 Scrollview 跟踪触摸滑动,保障界面滑动不受其余模式影响。
KCFRunloopCommonMode 这是一个占位符模式。它用作标记 KCFRunloopDefaultModeUITrackingRunnoopMode。这不是真正的模式。
UiinitializationRunloopMode app 刚启动时进入的第一个模式。启动后将不再应用。
CSeventreceiveRunloopMode 承受零碎事件的外部模式。通常不应用

runloop 启动时,只能抉择一种模式作为currentmode。如果须要切换模式,只能退出以后模式,从新抉择一个模式进入。

这里基于上面对 CFRunloopCRFunloopMode的了解,RunloopMode保留在 runloop 中,理论执行的工作保留在 RunloopMode 中。

3.RunloopMode

RunloopMode 存储了 runloop 要解决的事件源。事件源有 3 种:Southtimerobserver

名称 形容
South 事件源
timer 定时器
observer 监听者

1) CFRunloopSourceRefSouth 事件的起源。看看它的定义。

struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; // 用于标记 Signaled 状态,source0 只有在被标记为 Signaled 状态,才会被解决
pthread_mutex_t _lock;
CFIndex _order; // immutable
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; // immutable, except invalidation
CFRunLoopSourceContext1 version1; // immutable, except invalidation
} _context;
};

CFRunloopSourceRef 是 runloop 要解决的事件源之一。Version0Version1source0source1 依据不同事件的解决来辨别的。

2)CFRunLoopTimerRefrunloop的相干定时器事件和定时器,定时执行一个工作,也在 runloop 中解决。

struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits; // 标记 fire 状态
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; // 增加该 timer 的 runloop
CFMutableSetRef _rlModes; // 寄存所有 蕴含该 timer 的 mode 的 modeName,意味着一个 timer 可能会在多个 mode 中存在
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; // 现实工夫距离 /* immutable */
CFTimeInterval _tolerance; // 工夫偏差 /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

3)CFRunLoopObserverRefCFRunloopObserverRef 是 runloop 的监听器,能够监听 runloop 的状态变动。

struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};

Runloop 在运行时有以下状态:


【面试点】
您能够将察看后果增加到 runloop 中。通过监控 runloop 的状态来判断是否卡顿。创立 observer 观察者,将创立的 observer 退出到主线程 runloop 的一般模式中,并创立一个间断的子线程来监控主线程的 runloop 状态。一旦发现睡眠前的 kCFRonloopBeforeSource 状态或者唤醒后的 kCFRonloopFafterWaiting 状态,在设定的工夫阈值内没有变动,就能够判断为卡住了,转储堆栈的信息,从而进一步剖析哪个办法有执行工夫长。
以上为 runloop 的根本数据结构剖析。

4、Runloop 是如何运行的?

首先,如何创立一个 runloop?其实 runloop 不须要手动创立。任何 runloop 都与一个线程相关联。先有线程,后有 runloop。Apple 提供了两个 API。让咱们获取 runloop、CFRunloopGetMain()CFRunloopGetCurrent()。这两个办法别离获取以后线程的mainrunlooprunloop

从下面两个函数能够看出,runloop是通过 _CFRunloopGet0(),它以线程为参数,与通过 key 从NSDictionary 中获取值十分类似。接下来看看 _CRFunloopGet0() 的实现。

获取线程的 runloop。首先,以线程为 key,从全局字典中查找。如果没有找到,就新建一个,以 threadkeyrunloopvalue 保留到全局字典中(如果全局字典不存在,先初始化全局字典,并创立 MainRunloop 保留到全局字典中)。以下是源代码,我增加了正文。

以上就是获取以后 runloop 的原理,以及工作在 runloop 外部是如何执行的。这是一个图表。

CFRunlooprunCFRunloopRuninMode 都在外部调用 CFRunloopRunspecificCFRunloopRunspecific外部调用 __CFRunloopRunCFRunloopRunspecific__CFRunloopRun一起就是 runloop 的残缺实现。看看上面的伪代码解释,就是 runloop 的外部逻辑:

Runloop 和线程的关系

  1. Runloop 存储在一个全局字典中。线程作为 key,runloop 作为 Value。
  2. 第一次创立线程时,没有 runloop 对象。Runloop 将在第一次获取时创立。
  3. Runloop 在线程完结时会被销毁。
  4. 主线程的 runloop 曾经主动获取(创立),子线程默认不开启 runloop。
  5. 每个线程都有一个惟一的 runloop 对象与之对应。
  6. 先有线程,后有 runloop。

在日常开发中如何应用 Runloop

1. 控制线程生命周期(线程放弃流动,线程永远放弃)。原理:如果模式中没有 soure0/source1/timer/observer,runloop 会立刻退出。因而,为了不让它退出,能够在 runloop 中增加一个soure1。这是 af2 中应用的原理。
上面 AFNetworking 的一个例子:

  1. UITableView 提早加载图片。将 SETIMAGE 置于 NSDefaultRunloopMode 中,即滑动时不会调用调配图片的办法,但滑动后切换到 NSDefaultRunloopMode 才会调用。

[self.img performSelector:@selector(setImage:) withObject:image afterDelay:0inModes:[NSDefaultRunLoopMode]];

3. 解决了滑动时 NSTimer 进行工作的问题 (将timer 增加到 NSRunLoopCommonModes 模式)。

NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerEvent) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

默认状况下,计时器处于 NSDefaultRunloop 模式。当咱们滑动页面的时候,runloop会切换到 UITracking runloopmode,所以咱们的定时器会进行工作。就像商场里的倒计时一样,当咱们滑动页面时,倒计时就会进行。为了解决这个问题,咱们须要让定时器工作在UITracking runloopmode,和NSRunloopCommonModes,这个模式相当于NSDefaultRunloopModeUITrackingRunloopMode的组合。因而,为定时器指定 NSRunnoopcommonModes 模式,使定时器能够在 NSDefaultRunloopModeUITrackingRunnloopMode两种模式下运行。

  1. 另外,能够通过监控 runloop 的状态来解决卡顿问题。

runloop的进入 sleep 前和唤醒后的两个 loop 状态定义的值别离是 kCFRenloopbeforesourceskCFRenloopafterwaiting,即触发 source0 回调接管 mach_port音讯有两种状态。创立 observer 观察者,将创立的 observer 退出到主线程 runloop 的一般模式中,并创立一个间断的子线程来监控主线程的 runloop 状态。一旦发现睡眠前的 kCFRonloopBeforeSource 状态或者唤醒后的 kCFRonloopFafterwaiting 状态,在设定的工夫阈值内没有变动,就能够判断为卡住了,转储堆栈的信息,从而进一步剖析哪个办法有执行工夫长。

以上是开发中罕用的 runloop 相干利用。

正文完
 0