概述
RunLoop
顾名思义,就是运行循环
,在程序运行过程中循环做一些事情。
RunLoop的基本作用
- 保持程序的持续运行
- 处理 App 中的各种事件(比如触摸事件、定时器事件等)
- 节省 CPU 资源,提高程序性能:该做事时做事,该休息时休息
- ......
应用范畴
- 定时器(
Timer
)、PerformSelector
GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
AutoreleasePoll
如果没有 RunLoop
,那么程序执行完就退出了,比如下面代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
如果有了 RunLoop
,程序并不会马上退出,而是保持运行状态。比如类似下面伪代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int retVal = 0;
do {
// 睡眠中等待消息
int message = sleep_and_wait();
// 处理消息
retVal = process_message(message);
} while (0 == retVal);
}
return 0;
}
RunLoop对象
iOS 中有 2
套 API 来访问和使用 RunLoop
:
Core Foundation
框架中的CFRunLoopRef
。Foundation
框架中的NSRunLoop
。
NSRunLoop
是基于 CFRunLoopRef
的一层 OC
包装,并且 CFRunLoopRef
是开源的。源码地址:https://opensource.apple.com/tarballs/CF/
获取 RunLoop
对象:
// 获取当前线程RunLoop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
CFRunLoopRef currentRunLoopRef = CFRunLoopGetCurrent();
// 获取主线程RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
CFRunLoopRef mainRunLoopRef = CFRunLoopGetMain();
通过 lldb
指令,也可以验证 NSRunLoop
是对 CFRunLoopRef
的包装:
(lldb) p/x currentRunLoop->_rl
(__NSCFType *) $1 = 0x0000600003b7c500
(lldb) p/x currentRunLoopRef
(CFRunLoopRef) $2 = 0x0000600003b7c500
RunLoop与线程
每条线程都有唯一
的一个与之对应的 RunLoop
对象。
RunLoop
保存在一个全局的 Dictionary
里,线程作为 key
,RunLoop
对象作为 value
。
创建
子线程
刚创建时并没有 RunLoop
对象,RunLoop
会在第一次获取它使创建。
主线程
的 RunLoop
是在 main
函数中调用的 UIApplicationMain
函数中创建的。
销毁
RunLoop
会在线程结束时销毁。
RunLoop创建和获取的源码
// 获取RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
// 根据线程获取RunLoop
return _CFRunLoopGet0(pthread_self());
}
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
// 根据线程获取RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
// 如果字典为空,则创建存储RunLoop的全局字典
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 从字典中根据线程获取RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果从字典中根据线程获取RunLoop失败,则根据线程创建RunLoop并存储到字典中
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相关的类
Core Foundation
中关于 RunLoop
的 5
个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
CFRunLoopModeRef
代表 RunLoop
的运行模式。
一个 RunLoop
包含若干个 Mode
,每个 Mode
又包含若干个 Source0
/Source1
/Timer
/Observer
。
RunLoop
启动时只能选择其中一个 Mode
,作为 currentMode
。
如果需要切换 Mode
,只能退出当前 Loop
,再重新选择一个 Mode
进入。不同组的 Source0
/Source1
/Timer
/Observer
能分隔开来,互不影响。
如果 Mode
里没有任何 Source0
/Source1
/Timer
/Observer
,RunLoop
会立马退出。
CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; // 当前的模式
CFMutableSetRef _modes; // 所有的模式,CFRunLoopModeRef
//...
};
CFRunLoopModeRef
常见的 Mode
:
kCFRunLoopDefaultMode
(NSDefaultRunLoopMode
):App 的默认 Mode
,通常主线程是在这个 Mode
下运行。
UITrackingRunLoopMode
:界面跟踪 Mode
,用于 ScrollView
追踪触摸滑动,保证界面滑动时不受其他 Mode
影响。
kCFRunLoopCommonModes
(NSRunLoopCommonModes
):它并不是一种模式,而是一种标记。表示可以在 kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
模式中运行。
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0; // CFRunLoopSourceRef
CFMutableSetRef _sources1; // CFRunLoopSourceRef
CFMutableArrayRef _observers; // CFRunLoopObserverRef
CFMutableArrayRef _timers; // CFRunLoopTimerRef
};
CFRunLoopSourceRef
source
是 事件源
不是 事件
。
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order;
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0;
CFRunLoopSourceContext1 version1;
} _context;
};
CFRunLoopObserverRef
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; // 状态
CFIndex _order;
CFRunLoopObserverCallBack _callout;
CFRunLoopObserverContext _context;
};
监听RunLoo状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
NSLog(@"default");
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
CFRunLoopTimerRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval;
CFTimeInterval _tolerance;
uint64_t _fireTSR;
CFIndex _order;
CFRunLoopTimerCallBack _callout;
CFRunLoopTimerContext _context;
};
RunLoop图示
RunLoop的运行逻辑
Source0
- 触摸事件处理
performSelector:onThread:
Source1
- 基于
Port
的线程间通信
- 系统事件捕捉,比如点击屏幕后
Sources1
捕捉后分发到Source0
处理。
Timers
NSTimer
performSelector:withObject:afterDelay:
Observers
- 用于监听
RunLoop
的状态 - UI 刷新(
BeforeWaiting
),在要睡觉前处理 UI 刷新。 Autorelease Pool
(BeforeWaiting
),在要睡觉前处理自动释放池。
执行流程
01、通知 Observers:进入 Loop
02、通知 Observers:即将处理 Timers
03、通知 Observers:即将处理 Sources
04、处理 Blocks
05、处理 Source0(可能会再次处理 Blocks)
06、如果存在 Source1,就跳转到第8步
07、通知 Observers:开始休眠(等待消息唤醒)
08、通知 Observers:结束休眠(被某个消息唤醒)
01> 处理 Timer
02> 处理 GCD Async To Main Queue
03> 处理 Source1
09、处理 Blocks
10、根据前面的执行结果,决定如何操作
01> 回到第02步
02> 退出 Loop
11、通知 Observers:退出 Loop
执行流程图示
RunLoop休眠原理
下面代码也是 阻塞线程
,但是会一直持续占用 CPU
资源:
while (1) ;
而 RunLoop
的 阻塞线程
能实现真正的睡眠,不会持续占用 CPU
资源。想要实现这种效果,只有 内核层面
的 API 才能做到。
在 Core Foundation
框架的 CFRunLoop.c
文件的 __CFRunLoopServiceMachPort
函数中,通过 mach_msg()
函数向内核层发送线程休眠的消息,等待其他消息来唤醒休眠的线程。