一 什么是Runloop
二 Runloop的运行逻辑
三 Runloop在实际开发中的应用
一 什么是Runloop
1.1 Runloop的基本认识
RunLoop是一个接收处理异步消息事件的循环,一个循环中:等待事件发生,然后将这个事件送到能处理它的地方。
1.1.1 它的定义有两个点:
1 它是一个运行循环,like while(1),它是保证程序一直运行不退出的基本条件
2 在这个循环里面有事情时它就会处理,没事情时它就休息,既保证了一些有些操作它能及时相应,又保证了没活干时不浪费系统资源。
1.1.2 那么Runloop它在app中都有哪些作用?
1 保持程序的持续运行
2 事件响应、手势识别、界面刷新
3 AutoreleasePool自动释放池
4 定时器(Timer)、PerformSelector 定时器,延迟执行操作
5 GCD Async Main Queue 线程主队列里的操作
6节省CPU资源,提高程序性能:该做事时做事,该休息时休息
1.2 runloop底层相关对象介绍
iOS中有2套API来访问和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/
Core Foundation中关于RunLoop的有5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
下面我们来逐一介绍
1.2.1 CFRunLoopRef RunLoop对象
CFRunLoop.h
typedef struct __CFRunLoop * CFRunLoopRef;
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;
};
下面我们介绍一下RunLoop对象里几个主要的字段:
1.2.2 pthread_t _pthread
线程对象,一个runloop对象里面就有一个线程对象,RunLoop是来管理线程的,当线程的RunLoop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务.稍后我们再详细讲解线程方面的东西
1.2.3 CFMutableSetRef _commonModes
通用模式,首先什么是模式,就是一种运行环境,就像听歌,有单曲模式,循环模式,随机模式,runloop也有运行模式,界面静止处于一种模式,界面滑动又处于另一种模式,模式之间相互独立互不影响,runloop 同时只能在一种模式下运行,这就保证了界面滚动滑动时,就处于一种滑动模式,专门干这件事,其他啥也不干,界面就会滑动很流畅。
1.2.4 CFMutableSetRef _commonModeItems;
如果一个任务运行在_commonModes下运行那么它就会被加入到_commonModeItems里面比如一个定时器为了保证滑动不卡住,我们会让它在UITrackingRunLoopMode也能运行,这就需要它在_commonModes下运行,那么这个定时器就会被加入到_commonModeItems里,runloop在处理这个定时器任务时,它就会从_commonModeItems里找出这个任务去执行
1.2.5 CFRunLoopModeRef _currentMode
runloop当前所处的模式
1.2.6 CFMutableSetRef _modes
RunLoop所有运行模式列表,它主要有以下5个:
kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode,它是kCFRunLoopDefaultMode,UITrackingRunLoopMode 并集的标识。
UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后不再使用
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
下面让我们来看看具体的模式是什么样的
1.2.7 RunLoop通用模式有两种:
1 KCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
2 UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
接下来让我们看看模式对象里面都有些什么
CFRunLoopModeRef
CFRunLoop.c
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
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; // 事件源0
CFMutableSetRef _sources1; // 事件源1
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 */
};
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer下面我们解释下这几个字段具体作用
1 Source0
触摸事件处理
performSelector:onThread:
2 Source1
基于Port的线程间通信
系统事件捕捉
3 Timers
NSTimer
performSelector:withObject:afterDelay:
4 Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)
下面我们总结下模式结构,如图:
- 1 RunLoop启动时只能选择其中一个Mode,作为currentMode
- 2 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
- 3 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
- 4 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
1.3 CFRunLoopSourceRef
runloop.c
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
事件源对象
1 Source0
触摸事件处理,点击滑动等
performSelector:onThread: // 在指定一个线程执行任务
2 Source1
基于Port的线程间通信
系统事件捕捉
1.4 CFRunLoopTimerRef
runloop.c
__CFRunLoopTimer * CFRunLoopTimerRef;
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
定时器,就是定时执行一个任务,也是在runloop中处理的
1.5 CFRunLoopObserverRef
runloop.c
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变,如下:
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理 Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理 Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"从休眠中唤醒loop");
break;
case kCFRunLoopExit:
NSLog(@"即将退出runloop");
break;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer,kCFRunLoopDefaultMode);
/// 这里可以自己写一个NSTimer实验一下
// 释放runloop
CFRelease(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
kCFRunLoopAllActivities = 0x0FFFFFFFU// 包含所有以上状态
};
以上就是RunLoop相关对象的介绍,下面我们来讲一下RunLoop的运行逻辑。
二 Runloop的运行逻辑
官网上有一张图是这样的:
这张图讲了RunLoop 循环处理source0 source1 timer事件,他并没有讲清楚具体的执行步骤,以及线程休眠的细节,接下来看一张详细的执行图:
它的具体执行步骤如下:
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
下面让我们看一下,简化的源码执行流程
// 共外部调用的公开的CFRunLoopRun方法,其内部会调用CFRunLoopRunSpecific
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 经过精简的 CFRunLoopRunSpecific 函数代码,其内部会调用__CFRunLoopRun函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知Observers : 进入Loop
// __CFRunLoopDoObservers内部会调用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函数
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop逻辑
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers : 退出Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
// 精简后的 __CFRunLoopRun函数,保留了主要代码
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 通知Observers:即将处理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers:即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理Sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 如果有Sources1,就跳转到handle_msg标记处
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
// 通知Observers:即将休眠
__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);
__CFRunLoopUnsetSleeping(rl);
// 通知Observers:已经唤醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
if (被Timer唤醒的) {
// 处理Timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
else if (被GCD唤醒的) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被Sources1唤醒的
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
}
// 执行Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 根据之前的执行结果,来决定怎么做,为retVal赋相应的值
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 线程休眠
Runloop的本质就是一个do,while循环,当有事做时做事,没事做时休眠。怎么休眠,这个是由系统内核来调度的,是真的休眠了cup不占用资源了。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
// Take care of all voucher-related work right after mach_msg.
// If we don't release the previous voucher we're going to leak it.
voucher_mach_msg_revert(*voucherState);
// Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
*voucherState = voucher_mach_msg_adopt(msg);
if (voucherCopy) {
if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
// Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
// CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
*voucherCopy = voucher_copy();
} else {
*voucherCopy = NULL;
}
}
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
2 runloop和线程的关系
1 每条线程都有唯一的一个与之对应的RunLoop对象
2 RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
// CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
4 RunLoop会在线程结束时销毁
5 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); //里面会创建主线程,并开启RunLoop
}
}
我们讲完了runloop具体执行流程,那么在平时开发中有哪些应用呢,我们简单的来看一下
三 Runloop在实际开发中的应用
- 控制线程生命周期(线程保活)
- 解决NSTimer在滑动时停止工作的问题
- 监控应用卡顿
- 性能优化
下面我们简单的举两个例子
3.1 控制线程生命周期(线程保活)
#import
typedef void (^MyPermenantThreadTask)(void);
@interface MyPermenantThread : NSObject
/**
开启线程
*/
- (void)run;
/**
在当前子线程执行一个任务
*/
- (void)executeTask:(MyPermenantThreadTask)task;
/**
结束线程
*/
- (void)stop;
@end
#import "MyPermenantThread.h"
/** MJThread **/
@interface MyThread : NSThread
@end
@implementation MyThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** MJPermenantThread **/
@interface MyPermenantThread()
@property (strong, nonatomic) MyThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation MyPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[MyThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; //如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出 所以为了不让它退出我们加了个source1 你也可以加其他的
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.innerThread start];
}
return self;
}
- (void)run
{
if (!self.innerThread) return;
[self.innerThread start];
}
- (void)executeTask:(MyPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[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:(MyPermenantThreadTask)task
{
task();
}
@end
#import "ViewController.h"
#import "MyPermenantThread.h"
@interface ViewController ()
@property (strong, nonatomic) MJPermenantThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJPermenantThread alloc] init];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.thread executeTask:^{
NSLog(@"执行任务 - %@", [NSThread currentThread]);
}];
}
- (IBAction)stop {
[self.thread stop];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
我们利用RunLoop的线程休眠机制实现了一个没任务就会休眠的线程MyPermenantThread, 只要你不调用stop 你就可以重复的使用这个线程来执行其他任务,很适合做大量串行执行的子任务,比如说有依赖的大量网络请求等。
3.2 解决NSTimer在滑动时停止工作的问题
static int number = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++number);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
通常我们写一个timer
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
这个时候timer默认处于NSDefaultRunLoopMode模式当我们滑动页面的时候RunLoop 会切换到UITrackingRunLoopMode模式,这样我们的timer就停止工作了,就像商城秒杀倒计时,滑动页面时倒计时就停止了,为了解决这个问题我们要让timer在UITrackingRunLoopMode下也能工作,所以我们给它指定了NSRunLoopCommonModes模式,这个模式标记这个timer可以在NSDefaultRunLoopMode、UITrackingRunLoopMode模式下运行。
3.3 监控应用卡顿
通过对 RunLoop 原理的分析,我们可以看出在整个过程中,loop 的状态包括 6 个,其代码定义 如下:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 进入 loop
kCFRunLoopBeforeTimers , // 触发 Timer 回调
kCFRunLoopBeforeSources , // 触发 Source0 回调
kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
kCFRunLoopExit , // 退出 loop
kCFRunLoopAllActivities // loop 所有状态改变
}
如果 RunLoop 的线程,进入睡眠前方法的执行时间过长而导致无法进入睡眠,或者线程唤醒后 接收消息时间过长而无法进入下一步的话,就可以认为是线程受阻了。如果这个线程是主线程的话,表现出来的就是出现了卡顿。
所以,如果我们要利用 RunLoop 原理来监控卡顿的话,就是要关注这两个阶段。RunLoop 在进 入睡眠之前和唤醒后的两个 loop 状态定义的值分别是 kCFRunLoopBeforeSources 和
kCFRunLoopAfterWaiting ,也就是要触发 Source0 回调和接收 mach_port 消息两个状态。
具体实现代码如下:
实现代码
好了我就讲这个两个例子至于监控应用卡顿,性能优化的应用,网上资料很多的,可自行查阅。