本文讲述iOS中Runloop的一些使用以及原理
Runloop对象
- iOS中有两套API来访问和使用Runloop
- Foundation : NSRunLoop
- Core Foundation : CFRunloopRef
- NSRunloop和CFRunloopRef都代表着Runloop对象
- NSRunloop是基于CFRunloopRef的一层OC包装
- CFRunloopRef是开源的
获取当前Runloop的两个方法
1 2
| NSRunLoop *runloop = [NSRunLoop currentRunLoop]; CFRunLoopRef runloopRef = CFRunLoopGetCurrent();
|
打印runloop可以发现类型是<CFRunLoop 0x600001994300 [0x101405a98]>
,跟runloopRef一致,但是打印地址不一样,原因是NSRunLoop
是对CFRunLoopRef
的封装,CFRunLoopRef
是存储在其内部,所以会不一样
Runloop与线程
每条线程都有唯一的一个与之对应的Runloop对象
Runloop保存在一个全局的Dictionary里,线程作为key,Runloop作为Value
- Runloop源码
CFRunloop.c
中我们可以找到CFRunLoopGetCurrent()
内调用了_CFRunLoopGet0()
,在这里面可以看到这行代码得以验证
1
| CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
|
线程刚创建的时候并没有Runloop对象,Runloop会在第一次获取它时创建
- 主线程一开始也是没有Runloop的,只是因为在
main.m
中调用了UIApplicationMain
函数,在这里面回去获取Runloop从而创建了Runloop
- 对应源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { ... if (!loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,pthreadPointer(t)); if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } __CFUnlock(&loopsLock); CFRelease(newLoop); } ... }
|
Runloop会在线程结束时销毁
主线程的Runloop已经自动获取(创建),子线程默认没有开启Runloop
Runloop相关的类
Core Foundation 中关于Runloop的5个类
- CFRunloopRef
- CFRunloopModeRef
- CFRunloopSourceRef
- CFRunloopTimerRef
- CFRunloopObserverRef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| typedef struct __CFRunLoop * CFRunloopRef;
struct __CFRunLoop { ... pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunloopModeRef _currentMode; CFMutableSetRef _modes; ... };
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode { ... CFStringRef _name; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; ... }
|
通过源码我们可以得知,一个Runloop对象里面有多个mode,存放在_modes
成员属性里面。其中有一个mode是_currentMode
。 然后每个mode都有name
、source0
、source1
等数组属性
- CFRunloopModeRef 代表Runloop的运行模式
- Runloop启动只能选择其中一个Mode作为currentMode
- 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
- 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
- 如果Mode里面没有任何Source0/Source1/Timer/Observer,Runloop会立马退出
CFRunloopModeRef
两种常见的Mode
kCFRunLoopDefaultMode
(NSDefaultRunloopMode
): App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode
界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
Source0
- 触摸事件处理
performSelector:onThread:
Source1
- 基于Port的线程间通信
- 系统事件捕捉(捕捉后到Source0去处理)
Timers
- NSTimer
performSelector:withObject:afterDelay:
Observers
- 用于监听Runloop的状态
- UI刷新(BeforeWaiting)在Runloop休眠之前会执行一次UI刷新
- Autorelease pool 在Runloop休眠之前自动释放某些内存
CFRunloopObserverRef
Runloop的几种状态
1 2 3 4 5 6 7 8 9 10
| typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), kCFRunLoopBeforeTimers = (1UL << 1), kCFRunLoopBeforeSources = (1UL << 2), kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopExit = (1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU };
|
我们可以自己添加一个监听者来监听Runloop的状态变化
- 定义监听回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 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: break; } }
|
- 创建监听者
1 2 3 4 5
| CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActivities, NULL);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); CFRelease(observer);
|
Runloop的运行逻辑
流程分析
- 通知Observers:进入Loop
- 通知Observers:即将处理Timers
- 通知Observers:即将处理Sources
- 处理Blocks(特指执行CFRunLoopPerformBlock内的block参数)
1 2 3
| CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{ });
|
- 处理Source0(可能会再次处理Blocks,因为有可能在处理Source0的时候添加了Blocks)
- 如果存在Source1,就跳转到第8步
- 通知Observers:开始休眠(等待消息唤醒)
- 通知Observers:结束休眠(被某个消息唤醒,被什么东西唤醒就处理什么东西)
- 处理Timer
- 处理GCD Async To Main Queue
- 处理Source1
- 处理Blocks
- 根据前面的执行结果,决定如何操作(如下几种可能)
- 回到第2步
- 退出Loop
- 通知Observers:退出Loop
源码分析
通过在控制台执行命令bt
我们可以看到开始是调用了CFRunLoopRunSpecific
函数,核心源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { ... __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
... return result; }
|
然后查看一下__CFRunLoopRun
内的核心处理代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { ... int32_t retVal = 0; do { __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); __CFRunLoopDoBlocks(rl, rlm); Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { goto handle_msg; } __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); do { __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); } while (1); __CFRunLoopSetIgnoreWakeUps(rl); __CFRunLoopUnsetSleeping(rl); __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); handle_msg:; if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { __CFArmNextTimerInMode(rlm, rl); } } else if (livePort == dispatchPort) { CFRUNLOOP_WAKEUP_FOR_DISPATCH(); __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } else { CFRUNLOOP_WAKEUP_FOR_SOURCE(); __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; } __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; }
|
执行顺序跟我们上面提及的执行流程是相似的,最后的返回值如果是0的话,那么就继续循环,如果是其他值,那么就会退出循环,继而退出Runloop
上面提及的处理Timers函数,处理GCD函数和处理Source1的函数,都会调用到__CFRUNLOOP_IS_CALLING_OUT_TO_A_xxx_FUNCTION__
这个函数(这个xxx代表Observer、Block、Source0或者Timer等,见下表),在这个函数里面,再去执行对应的操作,比如UIKit的界面刷新、Foundation定时器的执行
执行的事情 |
调用的函数 |
进入Loop |
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION |
处理Blocks |
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK |
处理Source0 |
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION |
处理Timer |
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION |
处理GCD |
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE |
处理SOURCE1 |
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION |
GCD只有在回调到主线程的时候才会调用到Runloop的函数,比如下面这种情况
1 2 3 4 5
| dispatch_async(dispatch_get_global_queue(0,0), ^{ dispatch_async(dispatch_get_main_queue(), ^{ ... }); });
|
Runloop休眠的实现原理
当Runloop需要休眠的时候,会调用用户态的API,然后内部调用mach_msg()切换到内核态,当有消息的时候,就会从内核态切换回用户态的API去处理消息
用户态 : mach_msg() -> 内核态: mach_msg() -> 用户态:处理消息
内核态:
- 等待消息
- 没有消息就让线程休眠
- 有消息就唤醒线程
NSTimer
由于NSTimer默认是运行在NSDefaultRunloopMode
的,所以在滚动的时候不会执行定时器,因为滚动的时候系统会切换到UITrackingRunLoopMode
这时候我们需要把NSTimer设置到NSRunLoopCommonModes
里,如下
1 2 3 4 5
| static int count = 0; NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"%d",++count); }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
NSDefaultRunloopMode
、UITrackingRunLoopMode
才是真正存在的模式
NSRunLoopCommonModes
并不是一个真的模式,它只是一个标记
timer能在_commonModes
数组中存放的模式下工作
1 2 3 4 5 6 7 8 9
| struct __CFRunLoop { ... pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunloopModeRef _currentMode; CFMutableSetRef _modes; ... };
|
线程保活
基本原理
一般情况下,我们创建线程后,当线程的任务执行完了,线程就会销毁,所以有时候我们需要让线程执行完任务后依旧存在,等到我们主动让他销毁他才会销毁。
首先我们创建一条线程
1
| self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
|
要让这条线程一直活着,我们可以让这个run
方法不要结束
这里添加一个Runloop,由于获取Runloop的时候就会创建Runloop,所以我们获取Runloop即可,然后调用addPort:forMode:
方法,添加一个Source0
这样子线程就会卡在[[NSRunLoop currentRunLoop] run];
这一行中,不会让方法执行完,线程也就不会销毁
1 2 3 4 5 6 7
| - (void)run { NSLog(@"%@ %s begin",NSThread.currentThread,__func__); [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; NSLog(@"%@ %s end",NSThread.currentThread,__func__); }
|
然后下一步我们需要调用一个方法去销毁这个线程,比如CFRunLoopStop(CFRunLoopGetCurrent())
,但是如果我们这么做
1 2 3 4 5 6
| - (void)stop { CFRunLoopStop(CFRunLoopGetCurrent()); }
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:NO];
|
发现线程不会销毁,原因是[[NSRunLoop currentRunLoop] run];
执行后,会在一个死循环内执行Runloop的runMode:beforeDate:
方法,类似于
1 2 3
| while(1) { [[NSRunLoop currentRunLoop] runMode: beforeDate:xxx]; }
|
CFRunLoopStop(CFRunLoopGetCurrent());
只是停止了其中的一次方法调用,进入下个循环后又会开启,所以用这个方法去完全停止Runloop是没用的
所以我们要使用别的方式去替代[[NSRunLoop currentRunLoop] run];
开启线程,因为这个run
方法是专门用于开启一个永不销毁的线程。
只需要改造一下上面那个while方法就好,给self添加一个bool属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @property(nonatomic,assign) BOOL isStop;
- (void)run { NSLog(@"%@ %s begin",NSThread.currentThread,__func__); [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while(!self.isStop) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"%@ %s end",NSThread.currentThread,__func__); }
- (void)stop { self.isStop = YES; CFRunLoopStop(CFRunLoopGetCurrent()); }
|
这样子我们调用run方法线程就会启动,调用stop方法,while循环就会退出,CFRunLoopStop(CFRunLoopGetCurrent());
停止了本次RunLoop,Runloop退出,这样子就能走到打印end那里,线程的方法走完了,线程就可以销毁了
封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| @interface MyThread : NSObject
- (void)run;
- (void)stop;
- (void)executeTask:(void(^)(void))block;
@end
@interface MyThread()
@property (nonatomic, strong) Thread *innerThread;
@property (nonatomic, assign, getter=isStopped) BOOL stopped;
@end
@implementation MyThread
- (instancetype)init { if (self = [super init]) { __weak typeof(self) weakSelf = self; self.innerThread = [[Thread alloc] initWithBlock:^{ [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf.isStopped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } }]; } return self; }
- (void)run { if (!self.innerThread) return; [self.innerThread start]; }
- (void)stop { if (!self.innerThread) return; [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES]; }
- (void)executeTask:(void (^)(void))block { if (!self.innerThread || !block) return; [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:block waitUntilDone:NO]; }
#pragma mark - private method
- (void)__stop {
self.stopped = YES; CFRunLoopStop(CFRunLoopGetCurrent()); self.innerThread = nil; }
- (void)__executeTask:(void (^)(void))block { block(); }
- (void)dealloc { [self stop]; NSLog(@"%s",__func__); }
@end
@interface Thread : NSThread @end
@implementation Thread
- (void)dealloc { NSLog(@"%s",__func__); }
@end
|