iOS Runloop底层详解

一 什么是Runloop
二 Runloop的运行逻辑
三 Runloop在实际开发中的应用
一 什么是Runloop
1.1 Runloop的基本认识

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

这张图讲了RunLoop 循环处理source0 source1 timer事件,他并没有讲清楚具体的执行步骤,以及线程休眠的细节,接下来看一张详细的执行图:


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
下面让我们看一下,简化的源码执行流程

// 共外部调用的公开的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 消息两个状态。
具体实现代码如下:
实现代码

好了我就讲这个两个例子至于监控应用卡顿,性能优化的应用,网上资料很多的,可自行查阅。

你可能感兴趣的:(iOS Runloop底层详解)