手动目录
- RunLoop 6大响应事件
- RunLoop 与线程的关系
- RunLoop状态监听
- RunLoop 数据结构
- RunLoop流程
- 如何进行休眠的
- RunLoop 与autoreleasePool
- RunLoop 与GCD
- RunLoop 应用
- 线程保活
什么是RunLoop?
RunLoop 是循环运行,在程序运行过程中,循环做一些事情。
RunLoop 是通过内部的运行循环,来对消息/事件进行管理的一个对象。
其作用是
1、保持程序的持续运行
2、处理APP中的各种事件(触摸、定时器、performSelector)
3、节省cpu资源、提供程序的性能:该做事就做事,该休息就休息
RunLoop 6大响应事件
- block:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
- 调用Timer:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- 响应source0:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- 响应
source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
- GCD主队列
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
- observer源:比如通知
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
当 RunLoop 进行回调时,一般都是通过一个很长的函数调用出去 (call out), 当你在你的代码中下断点调试时,通常能在调用栈上看到这些函数。下面是这几个函数的整理版本,如果你在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了:
可结合地步的【流程图】来看
{
/// 1. 通知Observers,即将进入RunLoop
/// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
/// 2. 通知 Observers: 即将触发 Timer 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 触发 Source0 (非基于port的) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即将进入休眠
/// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. 通知Observers,线程被唤醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. 如果是被Timer唤醒的,回调Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
/// 10. 通知Observers,即将退出RunLoop
/// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
RunLoop 与线程的关系
一一对应的关系。
- 在内部,线程-runloop 在内部以
key-value
的形式存储在一个全局字典中 。- 当然,子线程中中,如果不主动去获取,runloop是没有的。主线程中的runloop是在main函数的 UIApplicationMain中创建的。
NSRunLoop
是对 CFRunLoopRef
的包装,
在代码中,一般有这几种写法
NSRunLoop *mainLoop1 = [NSRunLoop mainRunLoop];
NSRunLoop *currentLoop1 = [NSRunLoop currentRunLoop];
CFRunLoopRef mainLoop2 = CFRunLoopGetMain();
CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
底层都是C语言代码,在追踪源码的时候,要用CFRunLoopGetMain
或者CFRunLoopGetCurrent
来追踪
我们在源码中能找到这样的代码:(源码不是工程,创建一个空工程,将源码拖进去)
// 追踪 CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
static CFMutableDictionaryRef __CFRunLoops = NULL;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
在源码中 我们看到,所有的runloop存在一个字典
CFMutableDictionaryRef
,
字典中 以key-value 的形式存取,CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
类似于这样
NSMutableDictionary *runloops = [NSMutableDictionary new];
runloops[@"祝线程"] = mainRunloop;
runloops[@"线程1"] = runloop1;
runloops[@"线程2"] = runloop2;
RunLoop状态监听
RunLoop 有几种状态
enum CFRunLoopActivity {
kCFRunLoopEntry = (1 << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1 << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1 << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1 << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1 << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1 << 7), // 即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 包含上面所有状态
};
RunLoop的状态是可以添加监听来看看是怎样的状态
// 方法1: 指定 执行方法的 方式
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
......
default:
break;
}
}
{
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
// 方法2: block 方式
{
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
RunLoop 数据结构
追踪源码 CFRunLoop
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
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 关键5个信息
_pthread
:
与之对应的线程_commonModes
:_commonModeItems
:
在commonModes状态下运行的对象(例如Timer)_currentMode
:
在当前loop下运行的mode(即使有多种mode,但同一时刻只能在一个mode下运行)modes
:
运行的所有模式----- 类似于CFMutableSetRef< CFRunLoopModeRef >
__CFRunLoopMode 关键的信息
_sources0
触摸事件处理
performSelector:onThread:withObject:waitUntilDone:_sources1
基于Port的线程间的通讯
系统事件捕捉(比如点击事件,由source1捕捉,然后包装之后交给source0处理)_observers
监听RunLoop的状态
UI刷新(在BeforeWaiting的时候刷新)
AutoreleasePool_timers
NSTimer
performSelector:withObject:、 afterDelay:
RunLoop 对应关系 RunLoop结构RunLoopMode 类型
NSDefaultRunLoopMode
App的默认ModeUITrackingRunLoopMode
滚动的时候处于这个modeNSRunLoopCommonModes
这是一个占位用的Mode,不是一种真正的Mode,它是上面2个mode的集合UIInitializationRunLoopMode
在刚启动App时进入的第一个Mode,启动完成后就不再使用GSEventReceiveRunLoopMode
接受系统事件的Mode,通常由系统去使用前三种mode 对外提供,后面的2中不能使用。
为什么 一个runloop里包含多个mode?
不同mode中的 source0、source1、timer、observe 能分割开来,互不影响。
RunLoop流程
流程问题,从run开始
(内部代码太多,贴一份精简的代码)
// CFRunLoopRun
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
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;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
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;
}
流程图
1、observers 进入loop(entry)
2、通知observer 即将处理Timer(BeforeTimers)
3、通知observer即将处理Sources (BeforeSources )
4、处理GCD
5、处理source0
- 5.1 如果source0 有GCD需要处理,就处理GCD
6、判断有没有端口消息(source1),有的话 继续处理
7、通知 observe即将进入休眠(BeforeWaiting),进入休眠之后进行事件监听。(会一直卡在这直到被其他唤醒)
8、通知observe结束休眠(AfterWaiting)
- 8.1、Timer唤醒 : 处理Timer
- 8.2、GCD唤醒:处理GCD
- 8.3、source1唤醒:处理Source1
9、执行GCD
10、根据之前的执行结果 决定loop是继续循环 还是退出
11、通知observe 退出(exit )。
这里有一个点需要注意 :
能够唤醒loop的有3个:Timer
、GCD
、Source1
;
Source0不能直接唤醒loop
source0是用户触摸事件,实际上是有系统(Source1)捕捉到点击事件,然后包装之后交给source0处理
如何进行休眠的
在用户层,是无法达到休眠(等待而不执行任何事物)的目的。通过内核去操作。
以下内容摘录于:深入理解RunLoop
mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图:
RunLoop 与autoreleasePool
- 即将进入Loop(kCFRunLoopEntry),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其order是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
- 准备进入休眠(kCFRunLoopBeforeWaiting),此时调用 _objc_autoreleasePoolPop() 对释放池里面的 对象进行release操作
- 即将退出Loop(kCFRunLoopExit)此时调用 _objc_autoreleasePoolPop()释放自动释放池。这个 Observer的order是2147483647,确保池子释放在所有回调之后。
RunLoop 与GCD
RunLoop与GCD的关系与2点:
1、在do..while 循环中,判断runloop有没有超时,用的是GCD的source_timer
2、当回主线程,也就是 GCD使用main_queue的时候,会向runloop发送一个_MAIN_DISPATCH_QUEUE的消息来唤醒runloop。这也是为什么子线程不能刷UI的原因之一。
- 子线程 的GCD不能做UI的刷新等操作。是因为在子线程中,无法唤醒runloop进行UI刷新。
我们做以下 操作:
执行这段代码,虽然设置backgroundColor的时候,颜色没有发生变化,但是当dispatch_after执行,或者主动执行runloop run 操作 的时候,颜色成功的改变了。- (void)task8 { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ self.view.backgroundColor = [UIColor redColor]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"1"); }); }); // [[NSRunLoop currentRunLoop] run]; }
RunLoop 应用
线程保活
上面说了runloop的原理。
那么就知道为什么有时候NSTimer不起效了。
RunLoop还能干什么? --线程保活
一般来说,我们在子线程里面执行完任务,子线程就会退出,但是,有时候我们需要在子线程多次执行任务,就需要线程一直不退出来节省消耗。
我们可以这样做。
------向子线程中 添加一个port (相当于向runloop中 添加了一个 source1)保证runloop中一直有事件要处理。
代码如下:
@property (nonatomic, strong) JEThread *thread;
@property (nonatomic, assign) BOOL stopped;
{
__weak typeof(self) weakSelf = self;
_thread = [[JEThread alloc] initWithBlock:^{ // iOS 10.0
[[NSRunLoop currentRunLoop ] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] run]; // 这个需要注意,内部会无限循环run, 不能手动停止
while (weakSelf && !weakSelf.stopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"-------end-------");
}];
[self.thread start];
}
- (void)stop {
if (!self.thread) return;
// 在子线程调用stop
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为NO
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
self.thread = nil;
}
- (void)dealloc {
[self stop];
NSLog(@"vc dealloc");
}
// 在子线程里处理自己的事情
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (!self.thread) return;
[self performSelector:@selector(doSomeThings) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)doSomeThings {
NSLog(@"1");
}
这里面有几个点需要注意:
-
1、为什么没有用
[runloop run]
而是用runMode:beforeDate
?
因为run 内部进行循环调用runMode:beforeDate
,我们需要自己打破这个循环,所以我们要进行自己的 while 条件循环。 -
2、
stop
方法里面 的 waitUntilDone 必须为YES?
考虑到可能会在dealloc里面调用stop,如果不等待,就会先释放了self,这个时候再进行 self.stopped 就是无效的。
参考 文章:
深入理解RunLoop 🔥🔥
网友评论