目录
-RunLoop的概念
-RunLoop逻辑与实现
-RunLoop在iOS中运用
-RunLoop实践
-RunLoop的概念
苹果在文档里,是这样定义RunLoop的 :
Run loops are part of the fundamental infrastructure associated with threads.
A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
RunLoop是与线程相关的基础功能,RunLoop是用于调度工作,并协调接收传入事件的事件处理循环。RunLoop的目标是让线程有任务时工作,没有任务处理时休眠。
RunLoop与线程的关系
线程在处理完自己的任务后一般会退出,为了实现线程不退出能够随时处理任务的机制被称为EventLoop,node.js 的事件处理,windows程序的消息循环,iOS、OSX的RunLoop都是这种机制。
线程和RunLoop是一一对应的,关系保存在全局的字典里。
在主线程中,程序启动时,系统默认添加了有kCFRunLoopDefaultMode 和 UITrackingRunLoopMode两个预置Mode的RunLoop,保证程序处于等待状态,如果接收到来自触摸事件等,就会执行任务,否则处于休眠中。
线程创建时并没有RunLoop,(主线程除外),RunLoop不能创建,只能主动获取才会有。RunLoop的创建是在第一次获取时,RunLoop的销毁是发生在线程结束时。只能在一个线程中获取自己和主线程的RunLoop。
RunLoop的挂起与唤醒堆栈调用

指定用于唤醒的 mach_port 端口调用 mach_msg 监听唤醒端口,被唤醒前系统内核将这个线程挂起,停留在mach_msg_trap状态。
由另一个线程向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续工作。
一个运行环接收来自两个不同类型的源事件。输入源传递异步事件,通常消息从另一个线程或从不同的应用程序。定时源提供的同步事件,在预定的时间发生的或重复间隔。这两种类型的源的使用应用特定处理例程,当它到达处理该事件。

上图显示了一个运行循环和各种来源的概念结构:
输入源传递异步事件到相应的处理程序,并调用
runUntilDate:方法(称为线程的相关NSRunLoop对象)退出。Timer 定时源提供的事件他们的处理程序例程,但不会导致运行循环退出。
-RunLoop逻辑与实现

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和Source1
Source0只包含了一个回调(函数指针),并不能主动触发事件,需要手动触发,
需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件
Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
CFRunLoopTimerRef 是基于时间的触发器,可以和NSTimer 混用,包含一个时间长度和回调,加入
RunLoop时,RunLoop会注册对应的时间点,当时间点时,RunLoop会被唤醒以执行那个回调
CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode, 1.0e10, false);
}
// 用指定的Mode启动,允许设置RunLoop超时时间int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
// RunLoop的实现int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
// 首先根据modeName找到对应mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode))
return;
// 1. 通知 Observers: RunLoop 即将进入 loop。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
// 内部函数,进入loop __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO; int retVal = 0; do {
// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 执行被加入的block __CFRunLoopDoBlocks(runloop, currentMode);
// 4. RunLoop 触发 Source0 (非port) 回调。 sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
// 执行被加入的block __CFRunLoopDoBlocks(runloop, currentMode);
// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
// 一个基于 port 的Source 的事件。
// 一个 Timer 到时间了
// RunLoop 自身的超时时间到了
// 被其他什么调用者手动唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port);
// thread wait for receive msg }
// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
// 收到消息,处理消息。 handle_msg:
// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件 else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
// 执行加入到Loop的block __CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
// 被外部调用者强制停止了 retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
// 如果没超时,mode里没空,loop也没被停止,那继续loop。
}
while (retVal == 0);
}
// 10. 通知 Observers: RunLoop 即将退出。 __CFRunLoopDoObservers(url, currentMode, kCFRunLoopExit);
}
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}

-RunLoop在iOS中运用
Run Loop的作用
1.使程序一直运行并接受用户输入
2.决定程序在何时应该处理那些Event
3.调用解耦(Message Queue)
4.节省CPU时间
何时调用
1.使用端口port或自定义输入源input sources和其他线程通信
2.使用线程的定时器timer
3.Cocoa中使用任何performSelector的方法
4.线程执行周期性任务
RunLoop应用场景
AutoreleasePool
-
主线程App运行时堆栈调用
E513FD63-F9BF-4101-A618-336239CA0194.jpg
程序启动时,苹果在主线程的RunLoop中添加了两个Observer用于监视RunLoop的kCFRunLoopEntry(唤起)和BeforeWating(准备休眠)
- RunLoopObserver与Autorelease Pool的关系
A13CAFDB-D73A-47D0-A229-22F88219630C.jpg
UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 对象释放。
实际开发中,在线程中添加RunLoop时一般也会加上@autoreleasepool
PerformSelecter
调用PerformSelecter:afterDelay方法时,系统会默认创建一个timer添加到当前线程中去,如果在线程中调用该方法,而没有获取currentRunLoop,则会失效
GCD
GCD与RunLoop的部分实现相互用到了对方,RunLoop的timer是用dispatch_source_t实现的,而GCD的dispatch_async()也用到了RunLoop
当调用dispatch_async(dispatch_get_main_queue(), block)时,dispatch会向主线程RunLoop发送消息,RunLoop会被唤醒,并从消息中获取block后回调
定时器 NStimer
NSTimer实际上就是CFRunLoopTimerRef,RunLoop不会非常准确的时间回调,延迟到程度跟当前线程是否繁忙有关,当线程空闲时,timer比较准时,线程繁忙时延迟会比较明显。Timer有个属性Tolerance宽容度,到达了某个时间点后容许有多少误差。
當創建一個Timer並添加到DefaultMode 時,Timer就會重復回調,此時滑動TableView時,RunLoop會將mode切換成TrackingRunLoopMode這時Timer就會被回調,並且也不會影響到滑動操作。如果需要Timer在多個mode下得到回調,有一個辦法就是將Timer分別加入這兩個Mode中,或者加入到頂層RunLoop的commonModeItems中去,commonModeItems被RunLoop自動更新到具有Common屬性的Mode里去。
网络请求
在基于CFNetwork的NSURLConnection发起的NSURLConnectionLoader线程中,runloop通过基于mach port的Source0接收来自底层CFSocket的通知,同时唤醒delegate和RunLoop来处理这些通知。
事件响应 用到了 __IOHIDEventSystemClientQueueCallback()
手势识别 用到了_UIGestureRecognizerUpdateObserver()
界面更新 用到了_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
等等
RunLoop实践
// RunLoop - 创建生命周期跟App相同的常驻线程
#pragma mark - 常驻线程
- (void)viewWillAppear:(BOOL)animated
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longRunLoop) object:nil];
[thread start];
}
- (void)longRunLoop
{
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
//RunLoop - 维护线程的生命周期,让线程不自动退出,isFinished为Yes时退出。 在启动RunLoop之前,必须添加监听的Port或输入源事件sources或者定时源事件timer,否则调用[runloop run]会直接返回,而不会进入循环让线程长驻。如果没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配。没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
@autoreleasepool {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
}
// RunLoop - 不断检测直到满足条件
#pragma mark - 不断检测直到满足条件
- (NSString *)userAgentString
{
NSString *string;
while (string == nil)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
return string;
}
// tableView延迟加载图片的新思路
#pragma mark - 图片延迟加载
- (void)imageViewLoad{
//ImageView的显示 滑动时不加载 只在NSDefaultRunLoopMode模式下显示图片
UIImageView *img;
[img performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
}
// RunLoop - 在一段时间内每隔一会执行某种任务的线程
//Run Loop 有 [acceptInputForMode:beforeDate:] 和[runMode:beforeDate:]方法来指定在一时间之内运行模式。如果不指定时间,Run Loop默认会运行在Default模式下(不断重复调用runMode:NSDefaultRunLoopMode beforeDate:...) 例如需要在应用启动之后,在一定时间内持续更新某项数据。
-(void)loopEvent{
@autoreleasepool {
//在30分钟内,每隔30s执行 run 方法
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
target:self
selector:@selector(run) userInfo:nil
repeats:YES];
[runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
}
// RunLoop - NSTimer 在不同模式下工作
NSTimer *timer = [NSTimer timerWithTimeInterval:3.0
target:self
selector:@selector(run)
userInfo:nil
repeats:YES];
// timerWithTimeInterval: 方式创建的如果不加入RunLoop,则不会执行
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
/或者/
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(doSomeThing)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//或 [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
[timer fire];
// RunLoop - NSTimer 在线程中创建一个定时器
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(createTimer) object:nil];
[thread start];
}
- (void)createTimer{
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(run)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
// RunLoop - ReactNative 框架中RCTWebSocket中用到的RunLoop
#pragma mark - ReactNative 框架中RCTWebSocket中用到的RunLoop
NSRunLoop *_runLoop;
dispatch_group_t _waitGroup;
- (void)main
{
_waitGroup = dispatch_group_create();
dispatch_group_enter(_waitGroup);
@autoreleasepool {
_runLoop = [NSRunLoop currentRunLoop];
dispatch_group_leave(_waitGroup);
NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:self selector:@selector(step) userInfo:nil repeats:NO];
[_runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { }
assert(NO);
}
}
- (void)step
{
// Does nothing
}
- (NSRunLoop *)runLoop;
{
dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER);
return _runLoop;
}
// RunLoop AFNetWorking之 AFURLRequestSerilization
#pragma mark - AFNetWorking之 AFURLRequestSerilization
//额外提供的一个方法,把request里的bodyStream写到文件
//即可以把multipart发送的内容先生成好保存到文件
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(void (^)(NSError *error))handler
{
NSParameterAssert(request.HTTPBodyStream);
NSParameterAssert([fileURL isFileURL]);
NSInputStream *inputStream = request.HTTPBodyStream;
NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
__block NSError *error = nil;
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//新开一条线程做这个事,stream的read和write是阻塞的
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
/*
*NSInputStream子类
NSURLRequest的setHTTPBodyStream接受的是一个NSInputStream*参数,那我们要自定义inputStream的话,创建一个NSInputStream的子类传给它是不是就可以了?实际上不行,这样做后用NSURLRequest发出请求会导致crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector。
这是因为NSURLRequest实际上接受的不是NSInputStream对象,而是CoreFoundation的CFReadStreamRef对象,因为CFReadStreamRef和NSInputStream是toll-free bridged,可以自由转换,但CFReadStreamRef会用到CFStreamScheduleWithRunLoop这个方法,当它调用到这个方法时,object-c的toll-free bridging机制会调用object-c对象NSInputStream的相应函数,这里就调用到了_scheduleInCFRunLoop:forMode:,若不实现这个方法就会crash。详见这篇文章。
*/
[inputStream open];
[outputStream open];
while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];
NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
if (inputStream.streamError || bytesRead < 0) {
error = inputStream.streamError;
break;
}
NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (outputStream.streamError || bytesWritten < 0) {
error = outputStream.streamError;
break;
}
if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}
//NSURLRequest怎么知道数据读完了?
//应该是这个函数返回0时外部就知道stream已经读完,调用它的close方法。
[outputStream close];
[inputStream close];
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(error);
});
}
});
//
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.HTTPBodyStream = nil;
return mutableRequest;
}
//AFNetworking之前的版本中AFURLConnectionOperationRunLoop用到了常驻线程
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
// 为了使接收delegate回调能够在后台线程中执行,且该线程不会提前被回收
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 在这里通过监听MachPort使线程不会回收,MachPort并没有发送消息
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start]; });
return _networkRequestThread;
}
//

相关资料:
官方文档 NSRunLoop Class Reference , CFRunLoop Reference.
CFRunLoopRef 的代码是开源的,你可以在这里http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看。
Swift 版跨平台的 CoreFoundation :https://github.com/apple/swift-corelibs-foundation/,这个版本的源码可能和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。

