RunLoop
- 运行循环
- 在程序运行过程中循环做一些事情
-
应用范畴
- 定时器(Timer)、
PerformSelector
GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
AutoreleasePool
- 如果没有RunLoop, 执行完代码后,会即将退出程序
- 定时器(Timer)、
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
- 如果有了RunLoop,程序并不会马上退出,而是保持运行状态
- 并不是说程序的运行需要runloop,runloop作用仅仅只是为了程序不立即退出,这点不能混淆了
- RunLoop的基本作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件,定时器事件等)
- 节约CPU资源,提高程序性能:该做事时做事,改休息时休息
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } //等价于 int main*(int argc, char *argv[]) { @autoreleasepool { int retVal = 0; do { int message = sleep_and_wait(); retval = process_message(message) } while(0 == retval) } }
RunLoop对象
- iOS中有2套API来访问和使用
RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
-
NSRunLoop
和CFRunLoopRef
都代表着RunLoop
对象-
NSRunLoop
是基于CFRunLoopRef
的一层OC包装 -
CFRunLoopRef
是开源的 - https://opensource.apple.com/tarballs/CF/
-
[NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获取主线程的RunLoop对象
CFRunLoopGetCurrent(); // 获取当前线程的RunLoop对象
CFRunLoopGetMain(); // 获取主线程的RunLoop对象
RunLoop与线程
- 每个runloop对应一个线程,并不是说一个线程就一定对应一个runloop
-
RunLoop
保存在一个全局的Dictionary
里,线程作为key
,RunLoop
作为value - 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
-
RunLoop
会在线程结束时销毁 - 主线程的
RunLoop
已经自动获取(创建),子线程默认没有开启RunLoop
(或默认没有runloop对象),所以子线程任务执行完毕之后,直接直接退出了 -
MainRunloop的运行逻辑发生在主线程
- 必须明确一点:什么是线程?线程就是代码执行的最小单元,既然runloop要执行while循环,那就必须在一个线程中执行
证明子线程中默认没有runloop对象
- 通过查看NSThread源码可知
- (void) start
{
pthread_attr_t attr;
if (_active == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on active thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_cancelled == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on cancelled thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_finished == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on finished thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
/* Make sure the notification is posted BEFORE the new thread starts.
*/
gnustep_base_thread_callback();
/* The thread must persist until it finishes executing.
*/
RETAIN(self);
/* Mark the thread as active while it's running.
*/
_active = YES;
errno = 0;
pthread_attr_init(&attr);
/* Create this thread detached, because we never use the return state from
* threads.
*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* Set the stack size when the thread is created. Unlike the old setrlimit
* code, this actually works.
*/
if (_stackSize > 0)
{
pthread_attr_setstacksize(&attr, _stackSize);
}
if (pthread_create(&pthreadID, &attr, nsthreadLauncher, self))
{
DESTROY(self);
[NSException raise: NSInternalInconsistencyException
format: @"Unable to detach thread (last error %@)",
[NSError _last]];
}
}
static void *
nsthreadLauncher(void *thread)
{
NSThread *t = (NSThread*)thread;
setThreadForCurrentThread(t);
/*
* Let observers know a new thread is starting.
*/
if (nc == nil)
{
nc = RETAIN([NSNotificationCenter defaultCenter]);
}
[nc postNotificationName: NSThreadDidStartNotification
object: t
userInfo: nil];
[t _setName: [t name]];
[t main];
[NSThread exit];
// Not reached
return NULL;
}
- (void) main
{
if (_active == NO)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-%@] called on inactive thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
[_target performSelector: _selector withObject: _arg];
}
GSRunLoopThreadInfo *
GSRunLoopInfoForThread(NSThread *aThread)
{
GSRunLoopThreadInfo *info;
if (aThread == nil)
{
aThread = GSCurrentThread();
}
if (aThread->_runLoopInfo == nil)
{
[gnustep_global_lock lock];
if (aThread->_runLoopInfo == nil)
{
aThread->_runLoopInfo = [GSRunLoopThreadInfo new];
}
[gnustep_global_lock unlock];
}
info = aThread->_runLoopInfo;
return info;
}
RunLoop相关的类
-
Core Foundation
中关于RunLoop
的5个类CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
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;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
CFRunLoopModeRef
-
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
会立马退出 - 常见的2种Mode
-
kCFRunLoopDefaultMode
(NSDefaultRunLoopMode
):App
的默认Mode
,通常主线程是在这个Mode下运行 -
UITrackingRunLoopMode
:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
-
CFRunLoopObserverRef
-
Observer
的监听状态
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
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
- (void)viewDidLoad {
[super viewDidLoad];
// 创建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的运行逻辑
- Source0
- 触摸事件处理
- performSelector:onThread:
- Source1
- 基于Port的线程间通信
- 系统事件捕捉
- Timers
- NSTimer
- performSelector:withObject:afterDelay:
- Observers
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- Autorelease pool(BeforeWaiting)
RunLoop的运行流程
- 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
void CFRunLoopRun(void) {
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) {
// 通知Observers:进入Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop逻辑
__CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
do {
// 通知Observer:即将处理timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observer:即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理Block
__CFRunLoopDoBlocks(rl, rlm);
// 处理Source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 处理Block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 判断是否有Source1,如果有,就跳转到handle_msg
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
didDispatchPortLastTime = false;
// 通知Observers:即将休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 进入休眠,等待其他消息唤醒
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
do {
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// 通知
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// 通知Observer:已经唤醒
__CFRunLoopUnsetSleeping(rl);
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
__CFRunLoopSetIgnoreWakeUps(rl);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// 被timer唤醒的,处理timer
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
else if (livePort == dispatchPort) { // 被GCD唤醒
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {// 被Source1唤醒的
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 执行Blocks
__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);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
RunLoop休眠的实现原理
- 等待消息
- 没有消息就让线程休眠
- 有消息就唤醒线程
- 在用户态和内核态之间来回切换,用户态唤醒线程处理消息,内核态休眠
- 休眠是调用了内核级的函数
mach_msg()
RunLoop在实际开中的应用
- 控制线程生命周期(线程保活)
- 解决NSTimer在滑动时停止工作的问题
- 监控应用卡顿
- 性能优化
线程保活
- 如果直接线程NSThread强引用的话,是不能办到线程保活的,因为线程的任务是在创建这个线程对象的时候传入的
- 任务结束之后,现在就结束了,要执行新的任务,就只能重新创建一个新的线程对象
- 所谓线程保活,就是为了让线程的生命周期与它被引用的对象生命周期一致
- 所以只能自定以一个线程
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[MJThread alloc] initWithBlock:^{
// 往RunLoop里面添加Source\Timer\Observer,
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// while里面的代码不会多次执行,因为runloop已经让线程休眠了
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
// [[NSRunLoop currentRunLoop] run];
}];
[self.innerThread start];
}
return self;
}
- (void)executeTask:(MJPermenantThreadTask)task {
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop {
if (!self.innerThread) return;
// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MJPermenantThreadTask)task {
task();
}
RunLoop监听页面卡顿
- RunLoop处理事件的两个主要阶段
- kCFRunLoopBeforeSouces和KCFRunLoopBeforeWating之间
- KCFRunLoopAfterwaiting之后
- 如果每次检测runloop的状态一直停留在 kCFRunLoopBeforeSouces 或者 KCFRunLoopAfterwaiting,则runloop一直在处理事件(source0 source1)
- 步骤
- 创建RunLoopObserver
- 将Observer添加到main loop中
- 监听Observer的状态回调
- 状态变更一次。信号量+1
- 在子线程中开启一个while循环。通过信号量
dispatch_semaphore_wait
设置超时时间,信号量的值不为0,要么是在runloop不断改变状态,要么就是等待超时了(超时了说明,RunLoop一直处于某一种状态) - 信号量不为0时,不断地去检查这个状态,如果这个状态是等于 kCFRunLoopBeforeSouces和KCFRunLoopBeforeWating,则出现卡顿
- 出现卡顿之后,就可以上报调用栈信息了
void runLoopOberCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
RunLoopObserverViewController *monitor = (__bridge RunLoopObserverViewController*)info;
monitor->activity = activity;
long st = dispatch_semaphore_signal(monitor->semaphore);
NSLog(@"dispatch_semaphore_signal:st=%ld,time:%@",st,[monitor getCurTime]);
if (activity == kCFRunLoopEntry) { // 即将进入RunLoop
NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopEntry");
} else if (activity == kCFRunLoopBeforeTimers) { // 即将处理Timer
NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeTimers");
} else if (activity == kCFRunLoopBeforeSources) { // 即将处理Source
NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeSources");
} else if (activity == kCFRunLoopBeforeWaiting) { //即将进入休眠
NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeWaiting");
} else if (activity == kCFRunLoopAfterWaiting) { // 刚从休眠中唤醒
NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAfterWaiting");
} else if (activity == kCFRunLoopExit) { // 即将退出RunLoop
NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopExit");
} else if (activity == kCFRunLoopAllActivities) {
NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAllActivities");
}
}
/**
# RunLoop处理事件的两个主要阶段
kCFRunLoopBeforeSouces和KCFRunLoopBeforeWating之间
KCFRunLoopAfterwaiting之后
- 如果每次检测runloop的状态一直停留在 kCFRunLoopBeforeSouces 或者 KCFRunLoopAfterwaiting,则runloop一直在处理事件(source0 source1)
*/
- (void)startMonitor {
semaphore = dispatch_semaphore_create(1);
CFRunLoopObserverContext context = { 0 , (__bridge void*)self, NULL, NULL };
obsever = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
true,
0,
runLoopOberCallback,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), obsever, kCFRunLoopCommonModes);
// 在子线程中监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (1) {
// dispatch_semaphore_wait 使信号量减一,如果超时了,则返回了一个非0的值,然后往下执行
// dispatch_semaphore_wait 往下执行的条件 减一之后,信号量不为0 或 等待超时
// runloop状态改变,使用信号量 +1, 这边wait之后,信号量不为0,要么是在runloop不断改变状态,要么就是等待超时了
long st = dispatch_semaphore_wait(self->semaphore, dispatch_time(DISPATCH_TIME_NOW, 3 *NSEC_PER_MSEC));
if (st != 0) { // 信号量超时了 - 即runloop的状态长时间没有发生变更,长期处于某一状态
if (!self->obsever) {
self->timeoutCount = 0;
self->semaphore = 0;
self->activity = 0;
return;
}
if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting) {
if (++self->timeoutCount < 5) {
continue; // 不足 5 次。直接 continue 当次循环,不将timtouotcount置为0
}
NSLog(@"-----卡顿了----上报栈信息");
self->timeoutCount = 0;
}
}
}
});
}
网友评论