美文网首页
iOS Runloop底层详解

iOS Runloop底层详解

作者: 当前明月 | 来源:发表于2019-12-15 15:39 被阅读0次
    一 什么是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 <Foundation/Foundation.h>
    
    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<UITouch *> *)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底层详解

          本文链接:https://www.haomeiwen.com/subject/rotvgctx.html