一、RunLoop探索剖析
1.概念
RunLoop 是什么?RunLoop 仍是比较望文生义的一个东西,说白了就是一种循环,只不过它这种循环比较高档。一般的 while 循环会导致 CPU 进入忙等候状况,而 RunLoop 则是一种“闲”等候
,这部分能够类比 Linux 下的 epoll。当没有事情时,RunLoop 会进入休眠状况,有事情产生时, RunLoop 会去找对应的 Handler 处理事情。RunLoop 能够让线程在需求做事的时分忙起来,不需求的话就让线程休眠。
附上官方RunLoop与线程的联系如下图:
2.结构体
直接上代码,检查结构:
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;
};
可见,一个RunLoop对象,首要包括了一个线程,若干个Mode
,若干个commonMode
,还有一个当时运转的Mode。
RunLoop Mode
一个Mode办理着各种事情,它的结构如下:
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 */
};
source0 是什么?app触发的事情,app办理的事情; UI事情
Runloop Mode 实际上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同组的 Source,Timer 和 Observer 阻隔开来。Runloop 在某个时刻只能跑在一个 Mode 下,处理这一个 Mode 傍边的 Source,Timer 和 Observer。
苹果文档中说到的 Mode 有五个,分别是:
- NSDefaultRunLoopMode
- NSConnectionReplyMode
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode
- NSRunLoopCommonModes
官网地址 翻译解释:
RunLoop Source
Run Loop Source分为Source、Observer、Timer三种,他们统称为ModeItem。
CFRunLoopSource CFRunLoopSource分source0和source1两个版本,它的结构如下:
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;
};
source0是App内部事情,由App自己办理的UIEvent、CFSocket都是source0。当一个source0事情预备执行的时分,必需求先把它符号为signal状况,以下是source0的结构体:
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
使用时,你需求先调用 CFRunLoopSourceSignal(source),将这个 Source 符号为待处理,然后手动调用 CFRunLoopWakeUp(runloop)
来唤醒 RunLoop,让其处理这个事情。
source1的结构体:
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
Source1除了包括回调指针外包括一个mach port,Source1能够监听系统端口和通过内核和其他线程通信,接收、分发系统事情,它能够主动唤醒RunLoop(由操作系统内核进行办理,例如CFMessagePort
音讯)。官方也指出能够自定义Source,因此关于CFRunLoopSourceRef来说它更像一种协议,框架已经默许定义了两种实现,如果有必要开发人员也能够自定义。
CFRunLoopObserver
CFRunLoopObserver是调查者,能够调查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 */
};
仿制代码
CFRunLoopObserver能够调查的状况有如下6种:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //行将进入run loop
kCFRunLoopBeforeTimers = (1UL << 1), //行将处理timer
kCFRunLoopBeforeSources = (1UL << 2),//行将处理source
kCFRunLoopBeforeWaiting = (1UL << 5),//行将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//被唤醒但是还没开始处理事情
kCFRunLoopExit = (1UL << 7),//run loop已经退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
CFRunLoopTimer
CFRunLoopTimer是定时器,能够在设定的时刻点抛出回调,它的结构如下:
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 */
};
所以CFRunLoopTimer具有以下特性:
- CFRunLoopTimer 是定时器,能够在设定的时刻点抛出回调
- CFRunLoopTimer和NSTimer是toll-free bridged的,能够相互转化
3.RunLoop原理
先看RunLoop流程图: 继续看runLoop 的源码: 这个do while 循环是有条件退出或者中止的。所以能够省cpu资源、提供程序的性能:该做事就做事,该歇息就歇息。
继续探索进入CFRunLoopRunSpecific
:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 告诉 Observers: RunLoop 行将进入 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
/// 告诉 Observers: RunLoop 行将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
在看中心函数__CFRunLoopRun
:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do { // itmes do
/// 告诉 Observers: 行将处理timer事情
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
/// 告诉 Observers: 行将处理Source事情
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
/// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
/// 处理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
/// 处理sources0回来为YES
if (sourceHandledThisLoop) {
/// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
/// 判别有无端口音讯(Source1)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
/// 处理音讯
goto handle_msg;
}
/// 告诉 Observers: 行将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
/// 等候被唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
/// 告诉 Observers: 被唤醒,结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被Timer唤醒) {
/// 处理Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
} else if (被GCD唤醒) {
/// 处理gcd
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else if (被Source1唤醒) {
/// 被Source1唤醒,处理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
/// 处理block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
小节
:
runloop依托当时的线程创建(如主线程loop
),接收_commonModeItems
使命集合,三种类型的使命(source、timer、observer
)根据不同的应用场景 _currentMode状况,切换runloop commonMode 的item 在等候,休眠的使命进入到loop中进行处理。
总结
-
结构
:一个RunLoop对象,首要包括了一个线程,若干个Mode
,若干个commonMode
,还有一个当时运转的Mode。- Runloop Mode 实际上是
Source,Timer 和 Observer 的集合
- Run Loop Source分为Source、Observer、Timer三种,他们统称为ModeItem。
- 这里注意Source0是非基于Port,
需求手动CFRunLoopWakeUp来唤醒
。 - 也就是与Source1的差异直接msg唤醒。
- Runloop Mode 实际上是
-
原理
:告诉进入runLoop,然后__CFRunLoopRun
起来,判别使命是Source,Timer ,Observer
,如果是Source1
直接直接唤醒,非Source1
的使命就进入休眠,等候msg音讯的唤醒音讯。都执行完结,进行符号回来状况,stop,finish。退出runLoop,do-while 完结。 -
应用
:检测UI卡顿,应用进程保活。
ps:祝大伙新的一年健健康康,兔飞猛进。
本文部分借鉴了大佬的文章:iOS RunLoop详解