美文网首页
RunLoop总结

RunLoop总结

作者: 斑驳的流年无法释怀 | 来源:发表于2018-08-19 23:22 被阅读27次

    什么是RunLoop

    就是个运行循环,在程序运行过程中循环做一些事情

    应用范畴

    • 定时器(Timer)、PerformSelector
    • GCD Async Main Queue
    • 事件响应、手势识别、界面刷新
    • 网络请求
    • AutoreleasePool

    有runloop能保证程序并不会马上退出,而是保持运行状态

    RunLoop的基本作用

    • 保持程序的持续运行
    • 处理App中的各种事件(比如触摸事件、定时器事件等)
    • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
    • ......

    iOS中有2套API来访问和使用RunLoop

    • Foundation:NSRunLoop
    • Core Foundation:CFRunLoopRef

    NSRunLoop和CFRunLoopRef都代表着RunLoop对象
    NSRunLoop是基于CFRunLoopRef的一层OC包装
    CFRunLoopRef是开源的

    RunLoop与线程

    • 每条线程都有唯一的一个与之对应的RunLoop对象

    • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value

    • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建

    • RunLoop会在线程结束时销毁

    • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

    获取RunLoop对象

    • Foundation
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
    
    • Core Foundation
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象
    

    RunLoop相关的类

    Core Foundation中关于RunLoop的5个类

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

    __CFRunLoop

    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;//runloop所在的线程
        uint32_t _winthread;
        CFMutableSetRef _commonModes; //commonMode集合   CFMutableSetRef _commonModeItems;//modeItem集合
        CFRunLoopModeRef _currentMode;//当前mode
        CFMutableSetRef _modes;//mode集合
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFAbsoluteTime _runTime;
        CFAbsoluteTime _sleepTime;
        CFTypeRef _counterpart;
    };
    

    __CFRunLoopMode

    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    
    struct __CFRunLoopMode {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
        CFStringRef _name;//ModeM名称
        Boolean _stopped;
        char _padding[3];
        CFMutableSetRef _sources0;//sources0
        CFMutableSetRef _sources1;//sources1
        CFMutableArrayRef _observers;//observers
        CFMutableArrayRef _timers;//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 */
    };
    
    

    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

    /* Run Loop Observer Activities */
    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
    };
    

    添加Observer监听RunLoop的所有状态

    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
        
    void observeRunLoopActicities(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;
        }
    }
    

    RunLoop的运行逻辑

    RunLoop的运行逻辑

    Source0

    • 触摸事件处理
    • performSelector:onThread:

    Source1

    • 基于Port的线程间通信
    • 系统事件捕捉

    Timers

    • NSTimer
    • performSelector:withObject:afterDelay:

    Observers

    • 用于监听RunLoop的状态
    • UI刷新(BeforeWaiting)
    • Autorelease pool(BeforeWaiting)

    处理流程如下:

    处理流程

    文字版:

    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
    

    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"test"); }];

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
      * frame #0: 0x000000010dfda543 Interview01-runloop流程`__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x000000010dfdc0a8, timer=0x000060c000175900) at ViewController.m:22
        frame #1: 0x000000010e3474dd Foundation`__NSFireTimer + 83
        frame #2: 0x000000010f1d7e64 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
        frame #3: 0x000000010f1d7a52 CoreFoundation`__CFRunLoopDoTimer + 1026
        frame #4: 0x000000010f1d760a CoreFoundation`__CFRunLoopDoTimers + 266
        frame #5: 0x000000010f1cee4c CoreFoundation`__CFRunLoopRun + 2252
        frame #6: 0x000000010f1ce30b CoreFoundation`CFRunLoopRunSpecific + 635
        frame #7: 0x00000001143bca73 GraphicsServices`GSEventRunModal + 62
        frame #8: 0x000000010f6c5057 UIKit`UIApplicationMain + 159
        frame #9: 0x000000010dfda6ef Interview01-runloop流程`main(argc=1, argv=0x00007ffee1c25120) at main.m:14
        frame #10: 0x0000000112ca5955 libdyld.dylib`start + 1
    

    CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
    说明是从Timer唤醒的runloop
    如果是点击事件唤醒则是:
    CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION

    // NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
    // NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
    // timer能在_commonModes数组中存放的模式下工作
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

    observer本来就是有的,自动释放池就是基于observer的,我们自己监听需要自己创建

    RunLoop休眠的实现原理

    RunLoop休眠的实现原理

    RunLoop在实际开中的应用

    1. 控制线程生命周期(线程保活)

    通过如下代码将线程进行保活

    #import "LQPermenantThread.h"
    
    /** LQThread **/
    @interface LQThread : NSThread
    
    @end
    
    @implementation LQThread
    
    - (void)dealloc
    {
        NSLog(@"%s", __func__);
    }
    
    @end
    
    /** LQPermenantThread **/
    @interface LQPermenantThread()
    
    @property (strong, nonatomic) LQThread *innerThread;
    
    @end
    
    @implementation LQPermenantThread
    
    #pragma mark - public methods
    
    - (instancetype)init
    {
        if (self = [super init]) {
            self.innerThread = [[LQThread alloc] initWithBlock:^{
                
                //创建上下文(要初始化一下结构体)
                CFRunLoopSourceContext context = {0};
                
                //创建source
                CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
                
                //往Runloop中添加source
                CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
                
                //销毁source
                CFRelease(source);
                
                //第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
                //启动
                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            }];
            
            [self.innerThread start];
        }
        return self;
    }
    
    - (void)executeTask:(LQPermenantThreadTask)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
    {
        CFRunLoopStop(CFRunLoopGetCurrent());
        self.innerThread = nil;
    }
    
    - (void)__executeTask:(LQPermenantThreadTask)task
    {
        task();
    }
    

    runloop的run方法相当于反复调用runMode:beforeDate:方法

    下面这种方式同样可以启动runloop

    // 创建上下文(要初始化一下结构体)
    CFRunLoopSourceContext context = {0};
                
    // 创建source
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
                
    // 往Runloop中添加source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
                
    // 销毁source
    CFRelease(source);
    // 启动
    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
    
    1. 解决NSTimer在滑动时停止工作的问题
      也就是将Timer添加到commonModes中

    2. 监控应用卡顿

    可以添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的

    1. 性能优化

    一次运行循环中只加载一张图片

    runloop源码分析

    // should only be called by Foundation
    // t==0 is a synonym for "main thread" that always works
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
        }
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
            __CFLock(&loopsLock);
        }
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);//创建loop
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
            // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
            __CFUnlock(&loopsLock);
        CFRelease(newLoop);
        }
        if (pthread_equal(t, pthread_self())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    
    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
        __CFRunLoopLock(rl);
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
        if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
        }
        volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
        CFRunLoopModeRef previousMode = rl->_currentMode;
        rl->_currentMode = currentMode;
        int32_t result = kCFRunLoopRunFinished;
        //具体要做的事情
        if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        //通知observers:退出runloop
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
            __CFRunLoopModeUnlock(currentMode);
            __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
        return result;
    }
    

    runloop执行源码

    do {
            //通知observers:即将处理Timers
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            //通知observers:即将处理Sources
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    
            //处理blocks
            __CFRunLoopDoBlocks(rl, rlm);
    
            //处理Source0
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            
            //处理blocks
            if (sourceHandledThisLoop) {
                __CFRunLoopDoBlocks(rl, rlm);
            }
    
            //判断有无Source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                    
                    //如果有Source1,就跳转处理,唤醒runloop
                    goto handle_msg;
            }
            
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        //没事就睡觉去了
        __CFRunLoopSetSleeping(rl);
    
            //处理Source1
            handle_msg:;
            __CFRunLoopSetIgnoreWakeUps(rl);
            //如果有timer,处理timer
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
            else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
    #endif
    #if USE_MK_TIMER_TOO
            else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
    #endif
            else if (livePort == dispatchPort) {
                //处理GCD
                CFRUNLOOP_WAKEUP_FOR_DISPATCH();
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            } else {
                //处理Source
                CFRUNLOOP_WAKEUP_FOR_SOURCE();
                if (rls) {
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            mach_msg_header_t *reply = NULL;
                    //处理Source1
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
    
        //处理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);
    
    • gcd不依赖runloop
      转到主线程,需要runloop
    • source1捕捉,source0处理
    • addport 相当于source1,如果mode中没有source、timer、observer就会死掉

    睡觉:内核层面的

    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 ... */
    
    

    相关文章

      网友评论

          本文标题:RunLoop总结

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