RunLoop总结

2018-08-19  本文已影响27人  斑驳的流年无法释怀

什么是RunLoop

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

应用范畴

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

RunLoop的基本作用

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

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

RunLoop与线程

获取RunLoop对象

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

RunLoop相关的类

Core Foundation中关于RunLoop的5个类

__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

常见的2种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

Source1

Timers

Observers

处理流程如下:

处理流程

文字版:

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);

睡觉:内核层面的

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

上一篇 下一篇

猜你喜欢

热点阅读