runloop总结

static CFMutableDictionaryRef __CFRunLoops = NULL;

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
    //如果__CFRunLoops存放loop的字典为NULL会初始化一个字典并且根据主线程创建mainLoop.
    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)) { 
        //把dict里面元素赋值给__CFRunLoops
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); //根绝线程从字典中取出loop
    __CFUnlock(&loopsLock);
    if (!loop) { 
    //如果从字典中取出的loop为NULL就创建一个newLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        //不知道为啥再从字典中根据线程取一遍loop上面不是已经取过了没有取到吗? 
        //这次肯定也是取不到的啊,取不到就把newLoop存到字典中
        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;
}

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    if (NULL == loop) {
    return NULL;
    }
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}

__CFRunLoops是一个字典已线程作为key CFRunLoopRef作为value

从以上代码分析可以看到runloop和线程的关系是一一对应的,但是子线程中的runloop默认是不存在的的 主动在子线程中调用[NSRunLoop currentRunLoop],子线程的runloop会自动创建,主线程的runloop会在__CFRunLoopCreate(pthread_t t)这个函数第一次被调用的时候创建

runloop结构

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主要结构

struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set里面存了若干个CFRunLoopModeRef
    ...
};

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set CFRunLoopSourceRef
    CFMutableSetRef _sources1;    // Set CFRunLoopSourceRef
    CFMutableArrayRef _observers; // Array CFRunLoopObserverRef
    CFMutableArrayRef _timers;    // Array 存CFRunLoopTimerRef
    ...
};

CFRunLoopModeRef代表RunLoop的运行模式

一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer

image.png

RunLoop启动时只能选择其中一个Mode,作为currentMode

如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入

不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响

如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

CFRunLoopObserverRef是观察者,每个 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
};

__CFRunLoop有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。

NSLog(@"runloop111 = %@",[NSRunLoop currentRunLoop]);
    NSLog(@"===============================");
    NSTimer *timer = [NSTimer timerWithTimeInterval:2 block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%@",@"111111");
    } repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    [timer fire];
    
    NSLog(@"runloop222 = %@",[NSRunLoop currentRunLoop]);
    NSLog(@"===============================");

我们这样先打印runloop 然后再吧timer按照NSRunLoopCommonModes的方式加到NSRunLoop中再打印runloop对比
我们发现timer加到runloop之后 _commonModeItems中首先会有这个timer 并且会吧_commonModeItems中所有的item同步到 具有CommonModes标记的真正的Mode

应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。

runloop源码的运行流程分析

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知Observers进入loop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 具体要做的事情
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知Observers退出loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}



static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    do {
        
      
        __CFRunLoopUnsetIgnoreWakeUps(rl);

        //将要处理timer
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //将要处理Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //处理block
    __CFRunLoopDoBlocks(rl, rlm);
        //处理Sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            //根绝处理Sources0的结果 来处理block
            __CFRunLoopDoBlocks(rl, rlm);
    }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                //如果有Sources1 跳转到handle_msg
                goto handle_msg;
            }
        }

        didDispatchPortLastTime = false;
        
        //通知Observers即将休眠
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl);
    // do not do any user callouts after this point (after notifying of sleeping)

        // Must push the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced.

        __CFPortSetInsert(dispatchPort, waitSet);
        
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

        do {
            if (kCFUseCollectableAllocator) {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            
            //等待别的消息来唤醒线程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);

        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);

        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

        // Must remove the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced. Also, we don't want them left
        // in there if this function returns.

        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);

        // user callouts now OK again 结束睡觉
    __CFRunLoopUnsetSleeping(rl);
     
        //通知DoObservers结束休眠
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        //这里可能是是 有Sources1直接过来的
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);

      if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            //被timer执行
            __CFArmNextTimerInMode(rlm, rl);
        }
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            //被timer执行
            __CFArmNextTimerInMode(rlm, rl);
        }
        else if (livePort == dispatchPort) {
            //被GCD唤醒
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            //处理Source1
             __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
            
    
    //处理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;
    }
        
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);


    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

1通知Observers进入loop
2通知Observers即将处理Timers
3通知Observers即将处理Sources
4处理Blocks
5处理Sources0(根据返回结果 可能再次处理Blocks)
6如果存在Sources1,直接到第8部
7通知Observers开始休眠(等待消息唤醒)
8通知Observers结束休眠(被某个消息唤醒)
---1处理Timer
---2处理GCD Async To Main Queue
---1处理Sources1
9处理Blocks
10根据前面的执行结果决定如何操作
---回到第二步
---退出Loop
11通知Observers退出Loop

image.png

Source0 触摸事件处理 performSelector:onThread:

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

Timers NSTimer performSelector:withObject:afterDelay:

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

runloop的使用场景

1线程保活

#import "LCPermenantThread.h"
#import "LCThread.h"

@interface LCPermenantThread ()

@property (nonatomic, strong) LCThread *thread;
@property (nonatomic, assign, getter=isStopped) BOOL stopped;
@end

@implementation LCPermenantThread

- (instancetype)init{
    if (self = [super init]) {
        self.stopped = NO;
        __weak typeof(self) weakSelf = self;
        self.thread = [[LCThread alloc]initWithBlock:^{
            [[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
            while (weakSelf && !weakSelf.isStopped) {
                NSLog(@"while+++");
                //这里面不要用strongSelf 因为 runloop会卡在这里用strongSelf就持有了self 必须手动stop runloop
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
                NSLog(@"while---");
            }
            NSLog(@"thread end");
        }];
    }
    return self;
}

- (void)run{
    if (!self.thread) {
        return;
    }
    [self.thread start];
}

- (void)executeTask:(LCPermenantThreadTask)task{
    if (!self.thread || !task) {
        return;
    }
    [self performSelector:@selector(__executeTask:) onThread:self.thread withObject:task waitUntilDone:NO];
}

- (void)stop{
    if (!self.thread) {
        return;
    }
    [self performSelector:@selector(__stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}

- (void)__executeTask:(LCPermenantThreadTask)task{
    task();
}

- (void)__stop{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.thread = nil;
}

- (void)dealloc{
    NSLog(@"%s",__func__);
    [self stop];
}

@end

2NSTimer注意事项

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 block:^(NSTimer * _Nonnull timer) {
        
    } repeats:YES];

以scheduledTimer开的头创建的的timer默认被加到了当前的runloop的default mode (schedules it on the current run loop in the default mode.)

NSTimer *timer  = [[NSTimer alloc] initWithFireDate:nil interval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
                    
                }];

NSTimer *timer  = [NSTimer timerWithTimeInterval:1 block:^(NSTimer * _Nonnull timer) {
                    
                } repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]

在timer fire之前必须把timer加入到runloop中 常用到的mode NSDefaultRunLoopMode UITrackingRunLoopMode 需要的注意的是NSRunLoopCommonModes并不是一种真正的mode 我们吧timer以这样"模式"加到runloop中(不记得Runloop的结构往上面找找)其实是加在了_commonModeItems里面了. 然后会查看_commonModes里的标记的mode(里面存的是"UITrackingRunLoopMode",和"kCFRunLoopDefaultMode"两个字符串),然后把timer从_commonModeItems复制到标记的Mode中的_timers数组中, 所以 UITrackingRunLoopMode模式下的mode和kCFRunLoopDefaultMode模式下都可以执行timer了

3卡顿检测
下面是检查卡顿的代码利用 监听runloop中Observer发出不同的LoopActivity. 利用信号量 超时过滤出 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting. 因为大部分的操作就是在Sources0和Sources1中处理的 上面这两个状态就是处理Sources0和Sources1之前的状态.

#import "LXDAppFluecyMonitor.h"
#import "LXDBacktraceLogger.h"


#define LXD_DEPRECATED_POLLUTE_MAIN_QUEUE


@interface LXDAppFluecyMonitor ()

@property (nonatomic, assign) int timeOut;
@property (nonatomic, assign) BOOL isMonitoring;

@property (nonatomic, assign) CFRunLoopObserverRef observer;
@property (nonatomic, assign) CFRunLoopActivity currentActivity;

@property (nonatomic, strong) dispatch_semaphore_t semphore;
@property (nonatomic, strong) dispatch_semaphore_t eventSemphore;

@end


#define LXD_SEMPHORE_SUCCESS 0
static NSTimeInterval lxd_restore_interval = 5;
static NSTimeInterval lxd_time_out_interval = 1;
static int64_t lxd_wait_interval = 200 * NSEC_PER_MSEC;


/*!
 *  @brief  监听runloop状态为before waiting状态下是否卡顿
 */
static inline dispatch_queue_t lxd_event_monitor_queue() {
    static dispatch_queue_t lxd_event_monitor_queue;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        lxd_event_monitor_queue = dispatch_queue_create("com.sindrilin.lxd_event_monitor_queue", NULL);
    });
    return lxd_event_monitor_queue;
}

/*!
 *  @brief  监听runloop状态在after waiting和before sources之间
 */
static inline dispatch_queue_t lxd_fluecy_monitor_queue() {
    static dispatch_queue_t lxd_fluecy_monitor_queue;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        lxd_fluecy_monitor_queue = dispatch_queue_create("com.sindrilin.lxd_monitor_queue", NULL);
    });
    return lxd_fluecy_monitor_queue;
}

#define LOG_RUNLOOP_ACTIVITY 0
static void lxdRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void * info) {
    SHAREDMONITOR.currentActivity = activity;
    dispatch_semaphore_signal(SHAREDMONITOR.semphore);
#if LOG_RUNLOOP_ACTIVITY
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"runloop entry");
            break;
            
        case kCFRunLoopExit:
            NSLog(@"runloop exit");
            break;
            
        case kCFRunLoopAfterWaiting:
            NSLog(@"runloop after waiting");
            break;
            
        case kCFRunLoopBeforeTimers:
            NSLog(@"runloop before timers");
            break;
            
        case kCFRunLoopBeforeSources:
            NSLog(@"runloop before sources");
            break;
            
        case kCFRunLoopBeforeWaiting:
            NSLog(@"runloop before waiting");
            break;
            
        default:
            break;
    }
#endif
};

@implementation LXDAppFluecyMonitor

#pragma mark - Singleton override
+ (instancetype)sharedMonitor {
    static LXDAppFluecyMonitor * sharedMonitor;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        sharedMonitor = [[super allocWithZone: NSDefaultMallocZone()] init];
        [sharedMonitor commonInit];
    });
    return sharedMonitor;
}

+ (instancetype)allocWithZone: (struct _NSZone *)zone {
    return [self sharedMonitor];
}

- (void)dealloc {
    [self stopMonitoring];
}

- (void)commonInit {
    self.semphore = dispatch_semaphore_create(0);
    self.eventSemphore = dispatch_semaphore_create(0);
}


#pragma mark - Public
- (void)startMonitoring {
    if (_isMonitoring) { return; }
    _isMonitoring = YES;
    CFRunLoopObserverContext context = {
        0,
        (__bridge void *)self,
        NULL,
        NULL
    };
    _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &lxdRunLoopObserverCallback, &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    
    dispatch_async(lxd_event_monitor_queue(), ^{
        while (SHAREDMONITOR.isMonitoring) {
            if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeWaiting) {
                __block BOOL timeOut = YES;
                NSLog(@"0");
                dispatch_async(dispatch_get_main_queue(), ^{
                    timeOut = NO;
                    dispatch_semaphore_signal(SHAREDMONITOR.eventSemphore);
                    NSLog(@"1");
                });
                NSLog(@"2");
                [NSThread sleepForTimeInterval: lxd_time_out_interval];
                NSLog(@"3");
                if (timeOut) {
                    NSLog(@"4");
                    [LXDBacktraceLogger lxd_logMain];
                }
                NSLog(@"5");
                dispatch_wait(SHAREDMONITOR.eventSemphore, DISPATCH_TIME_FOREVER);
                NSLog(@"6");
            }
        }
    });
    
    dispatch_async(lxd_fluecy_monitor_queue(), ^{
        //while一直在循环换速度很快
        //上面observer的回调函数会在runloop状态切换的时候发信号量
        //信号量的发布频率 如果小于lxd_wait_interval 说明在这个状态超时了
        //超时dispatch_semaphore_wait的返回值不为0
        //下面过滤了当前runloop状态是kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting两种状态超时超时了五次lxd_wait_interval(说明代码有卡顿)
        while (SHAREDMONITOR.isMonitoring) {
            long waitTime = dispatch_semaphore_wait(self.semphore, dispatch_time(DISPATCH_TIME_NOW, lxd_wait_interval)); //返回值不为0的时候   表示信号量超时
            if (waitTime != LXD_SEMPHORE_SUCCESS) {
                if (!SHAREDMONITOR.observer) {
                    SHAREDMONITOR.timeOut = 0;
                    [SHAREDMONITOR stopMonitoring];
                    continue;
                }
                if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeSources || SHAREDMONITOR.currentActivity == kCFRunLoopAfterWaiting) {
                    if (++SHAREDMONITOR.timeOut < 5) {
                        continue; //提前结束本次 while循环
                    }
                    [LXDBacktraceLogger lxd_logMain];
                    [NSThread sleepForTimeInterval: lxd_restore_interval];
                }
            }
            
            SHAREDMONITOR.timeOut = 0;
        }
    });
}

- (void)stopMonitoring {
    if (!_isMonitoring) { return; }
    _isMonitoring = NO;
    
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    CFRelease(_observer);
    _observer = nil;
}


@end

4性能优化
待更新

5点击事件也和runloop有关
ios进程通信 一个进程向Mach port发消息 另一个进程监听Mach port
我们知道runloop没有事情做的时候就会休眠,那我们一个点击事假是怎么唤醒runloop的呢? 中间都发生了什么事情了呢?

//runloop的睡眠是和个函数卡主了
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy)

iOS中有很多进程通信的方式Mach Ports,Distributed Notifications,Distributed Objects,XPC等等
这个函数等待接收mach_port消息

当我们触发了事件(触摸/锁屏/摇晃等)后
由IOKit.framework生成一个 IOHIDEvent事件
而IOKit是苹果的硬件驱动框架
由它进行底层接口的抽象封装与系统进行交互传递硬件感应的事件
它专门处理用户交互设备,由IOHIDServices和IOHIDDisplays两部分组成
其中IOHIDServices是专门处理用户交互的,它会将事件封装成IOHIDEvents对象,详细请看这里

然后这些事件又由SpringBoard接收,它只接收收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event
接着用mach port转发给需要的App进程

随后苹果注册的那个Source1就会触发回调,并调用 _UIApplicationHandleEventQueue()进行应用内部的分发
_UIApplicationHandleEventQueue()把IOHIDEvent处理包装成UIEvent进行处理分发.我们平时的UIGesture/处理屏幕旋转/发送给 UIWindow/UIButton 点击、touchesBegin/Move/End/Cancel这些事件,都是在这个回调中完成.
接下来就进入到寻找合适的view这个过程了
UIApplication->UIWindow->......->合适的hitView
找到合适的view就改响应事件了
合适的hitView-(没有响应事件)->superView-(没有响应事件)->...(所有superView都没有响应事件)...->UIWindow-(NO)-> UIApplication(默认没有响应)->丢弃这个UIEvent
点击事件总结借鉴(chaoxi) 哟_Json

触摸事件过程

触摸事件过程图片来自 JM_Seven

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343