Runloop

runloop的理解借助了MJ视频以及开源文档 

https://github.com/apple/swift-corelibs-foundation/

https://blog.csdn.net/u014795020/article/details/72084735

GNUstep源码下载地址:http://www.gnustep.org/resources/downloads.php

参考 https://www.jianshu.com/p/4c38d16a29f1/

CFRunLoopRef 的代码开源,且是NSRunLoopRef的封装基础,所以down它吧

staticCFMutableDictionaryRef __CFRunLoops =NULL;

staticCFLock_t loopsLock = CFLockInit;

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

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

        }

    }

    returnloop;

}

主要看它有用逻辑信息CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);全局字典存储着runloop,其中所在线程为key、 RunLoop为value;线程刚创建时并没有 RunLoop,RunLoop 的创建是发生在第一次获取时,销毁是发生在线程结束。你只能在线程内部获取 RunLoop。

runloop运行以model为基础

__CFRunLoopMode :

    CFMutableSetRef _sources0;

    CFMutableSetRef _sources1;

    CFMutableArrayRef _observers;

    CFMutableArrayRef _timers;

__CFRunLoop :

   CFMutableSetRef _commonModes;     // Set

    CFMutableSetRef _commonModeItems; //

    CFRunLoopModeRef _currentMode;    

    CFMutableSetRef _modes;        

这些统称为事件源,可以被同时加入多个 mode。如果没有事件源则 RunLoop 会直接退出。

runloop操作model 目前我们熟悉且公开看到的commonModel、defaultModel、trackingModel

//modeName为操作model的路径、当你没有的时候,会为你创建新的。。 

voidCFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) { 

    CHECK_FOR_FORK();

    if(__CFRunLoopIsDeallocating(rl))return;

    __CFRunLoopLock(rl);

    if(!CFSetContainsValue(rl->_commonModes, modeName)) {

CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) :NULL;

CFSetAddValue(rl->_commonModes, modeName);

if(NULL!= set) {

    CFTypeRef context[2] = {rl, modeName};

    /* add all common-modes items to new mode  commonmodel之所以会执行。因为commonmodel的item会同步到其它model中 */

    CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void*)context);

    CFRelease(set);

}

    }else{}    __CFRunLoopUnlock(rl);}

CFRunLoopRunInMode(CFStringRef modeName, ...);


Mode 暴露的管理 mode item 的接口有下面几个:

CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);

CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);

CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);

CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);

CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

item的说明:

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

};



借用mj的图来看执行流程

int32_t __CFRunLoopRun()

{

    // 通知即将进入runloop

    __CFRunLoopDoObservers(KCFRunLoopEntry);

    do

    {

        // 通知将要处理timer

        __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);

   // 通知将要处理source0

        __CFRunLoopDoObservers(kCFRunLoopBeforeSources);

        // 处理非延迟的主线程调用

        __CFRunLoopDoBlocks();

        // 处理Source0事件

        __CFRunLoopDoSource0();

        if (sourceHandledThisLoop) {

            __CFRunLoopDoBlocks();

         }

        /// 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。

        if (__Source0DidDispatchPortLastTime) {

            Boolean hasMsg = __CFRunLoopServiceMachPort();

            if (hasMsg) goto handle_msg;

        }

        /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。

        if (!sourceHandledThisLoop) {

            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);

        }

        // GCD dispatch main queue

        CheckIfExistMessagesInMainDispatchQueue();

        // 即将进入休眠

        __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);

        // 等待内核mach_msg事件

        mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();

        // 等待。。。

        // 从等待中醒来

        __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);

        // 处理因timer的唤醒

        if (wakeUpPort == timerPort)

            __CFRunLoopDoTimers();

        // 处理异步方法唤醒,如dispatch_async

        else if (wakeUpPort == mainDispatchQueuePort)

            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()

        // 处理Source1

        else    __CFRunLoopDoSource1();

        // 再次确保是否有同步的方法需要调用

        __CFRunLoopDoBlocks();

    } while (!stop && !timeout);

    // 通知即将退出runloop

    __CFRunLoopDoObservers(CFRunLoopExit);

}

__CFRunLoopRun内部其实是一个do while循环,只是不同于我们自己写的循环它在休眠时几乎不会占用系统资源,当然这是由于系统内核负责实现的,也是Runloop精华所在。

可以看到runloop进入休眠后需要item事件唤醒它 :  

 /// • 一个基于 port 的Source 的事件。

 /// • 一个 Timer 到时间了

  /// • RunLoop 自身的超时时间到了

  /// • 被其他什么调用者手动唤醒

基于mach_msg()通信。苹果系统层级结构。没有深入研究。。链接内可以看到更详细的。。。

摘个图


如图所示,首先我要明确一个知识点:runloop跑一圈,只能执行一个事件。

timer和source0进入runloop中,都只是通知Observer我要处理,但是还是会有6、7、8睡眠唤醒这一步。但是source1如果有,就会直接跳到第9步执行。

我们前面也讲过第7步,这里再提一下。它一直阻塞再这一列,直到:

 a.source1来了。b.定时器启动。c.runloop超时。d.runloop被显式唤醒CFRunLoopWakeUp(runloop)(也就是source0)来了。


实际中的app启动

系统一次性默认注册了5个Mode:

1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。

2. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。

3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。

4: GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。

5: kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。


Runloop和autoreleasePool

{

    /// 通知Observers,即将进入RunLoop

    /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();

 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);

   do {

/// 通知 Observers: 即将触发 Timer 回调。 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);

/// 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources); __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

/// 触发 Source0 (非基于port的) 回调。 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0); __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

/// 通知Observers,即将进入休眠 /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush(); __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);

///  sleep to wait msg. mach_msg() -> mach_msg_trap();

/// 通知Observers,线程被唤醒 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);

/// 如果是被Timer唤醒的,回调Timer __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);

/// 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);

///  如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);    }

while (...);

  /// 执行加入到Loop的block __CFRunLoopDoBlocks(runloop, currentMode);

if (sourceHandledThisLoop && stopAfterHandle) {

/// 进入loop时参数说处理完事件就返回。

retVal = kCFRunLoopRunHandledSource; }

else if (timeout) {

/// 超出传入参数标记的超时时间了

retVal = kCFRunLoopRunTimedOut; }

else if (__CFRunLoopIsStopped(runloop)) {

/// 被外部调用者强制停止了 retVal = kCFRunLoopRunStopped; }

else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {

/// source/timer/observer一个都没有了

retVal = kCFRunLoopRunFinished; }

/// 如果没超时,mode里没空,loop也没被停止,那继续loop。 }

while (retVal == 0); }

 通知 Observers: RunLoop 即将退出。

__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);  

AutoreleasePool

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer 即将进入、即将休眠。。

其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;

Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。


Runloop中的一些案例处理

事件响应

苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。

当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

手势识别

当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

界面更新

当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。

苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

 定时器

NSTimer 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。

如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。

CADisplayLink 是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。Facebook 开源的 AsyncDisplayLink 就是为了解决界面卡顿的问题,其内部也用到了 RunLoop,这个稍后我会再单独写一页博客来分析。

PerformSelecter 

当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

(performSelecter这里说的太泛泛了,其中指定线程执行的方法调用实际产生了source事件。。。)

关于GCD

 当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。

Runloop实际调用

对于NSRunloop来说:

// 运行NSRunLoop,运行模式为默认的NSDefaultRunLoopMode模式,没有超时限制

- (void)run;一般来说不建议使用。因为停止困难。。cfrunloopstop也不能关停。。

// 运行NSRunLoop:参数为时间期限,运行模式为默认的NSDefaultRunLoopMode模式

- (void)runUntilDate:(NSDate *)limitDate; 可以设置一个超时时间,过期自动停止

 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];//3秒后超时

// 运行NSRunLoop:参数为运行模式、时间期限,返回值为YES表示处理事件后返回的,NO表示是超时或者停止运行导致返回的。

 //这种方式的定义除了超时停止,还可以利用cfrunloopstop来停止,因为此方法就是cf第二种SInt32 CFRunLoopRunInMode的封装。。其次是利用返回值。出发source信号

- (BOOL)runMode:(NSString *)mode beforeDate:(NSDtate *)limitDate;

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];//持续运行。。

Rac中应用

do {  

[NSRunloop.mainRunloop runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];

} while(!done);

对于CFRunLoopRef来说

void CFRunLoopRun();

// 运行CFRunLoopRef:参数为运行模式、时间和是否在处理Input Source后退出标志,返回值是exit原因

SInt32 CFRunLoopRunInMode(mode, second, returnAfterSourceHandled);

// 停止运行CFRunLoop

void CFRunLoopStop(CFRunLoopRef rl);

// 唤醒CFRunLoopRef

void CFRunLoopWakeUp(CFRunLoopRef rl);

测试- (BOOL)runMode:(NSString *)mode beforeDate:(NSDtate *)limitDate退出状态

void)testDemo1{

    dispatch_async(dispatch_get_global_queue(0,0), ^ {

        NSLog(@"线程开始");

        // 获取当前线程

        self.thread = [NSThread currentThread];

        NSRunLoop *runloop = [NSRunLoop currentRunLoop];

        // 添加一个Port,同理为了防止runloop没事干直接退出

        [runloop addPort: [NSMachPort port] forMode: NSDefaultRunLoopMode];

        // 运行一个runloop, [NSDate distantFuture]:很久很久以后才让它失效

        [runloop runMode:NSDefaultRunloopMode beforeDate: [NSDate distantFuture]];

        NSLog(@"线程结束");

    });

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {

        // 在我们开启的异步线程调用方法

        [self performSelector:@selector(recieveMsg) onThread: self.thread withObject: nil waitUntilDone: NO];

    });

}

- (void)recieveMsg {

    NSLog(@"收到消息了,在这个线程:%@", [NSThread currentThread]);

}

cf中的方法 SInt32 CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled);

第三个参数是是否在处理事件后让Run Loop退出返回,NSRunLoop的第三种开启runloop的方法,综上所述,我们知道,实际上就是设置stopAfterHande这个参数为YES

关于返回值,我们知道调用runloop运行,代码是停在这一行不返回的,当返回的时候runloop就结束了,所有这个返回值就是runloop结束原因的返回,为一个枚举值,具体原因如下:

enum {

    kCFRunLoopRunFinished = 1, // Run Loop结束,没有Timer或者其他Input Source

    kCFRunLoopRunStopped = 2, // Run Loop被停止,使用CFRunLoopStop停止Run Loop

    kCFRunLoopRunTimedOut = 3, // Run Loop超时

    kCFRunLoopRunHandledSource = 4, // Run Loop处理完事件,注意Timer事件的触发是不会让Run Loop退出返回的,即使CFRunLoopRunInMode的第三个参数是YES也不行..

}

(void)testDemo2

{

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"starting thread.......");

        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(doTimerTask1:) userInfo:remotePort repeats:YES];

        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

        //最后一个参数,是否处理完事件返回,结束runLoop

        SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 100, YES);

        /*

        kCFRunLoopRunFinished = 1, //Run Loop结束,没有Timer或者其他Input Source

        kCFRunLoopRunStopped = 2, //Run Loop被停止,使用CFRunLoopStop停止Run Loop

        kCFRunLoopRunTimedOut = 3, //Run Loop超时

        kCFRunLoopRunHandledSource = 4 Run Loop处理完事件,注意Timer事件的触发是不会让Run Loop退出返回的,即使CFRunLoopRunInMode的第三个参数是YES也不行

        */

        switch (result) {

            case kCFRunLoopRunFinished:

                NSLog(@"kCFRunLoopRunFinished");

                break;

            case kCFRunLoopRunStopped:

                NSLog(@"kCFRunLoopRunStopped");

            case kCFRunLoopRunTimedOut:

                NSLog(@"kCFRunLoopRunTimedOut");

            case kCFRunLoopRunHandledSource:

                NSLog(@"kCFRunLoopRunHandledSource");

            default:

                break;

        }

        NSLog(@"end thread.......");

    });

}

- (void)doTimerTask1:(NSTimer *)timer

{

    count++;

    if (count == 2) {

        [timer invalidate];

    }

    NSLog(@"do timer task count:%d",count);

starting thread…….、、 do timer task count:1   do timer task count:2  kCFRunLoopRunFinished   end thread……. 

此时只有timer无效的情况下才退出runloop。。 


AsyncDisplayKit应用监听卡顿机制

AsyncDisplayKit 是 Facebook 推出的用于保持界面流畅性的框架,其原理大致如下:

UI 线程中一旦出现繁重的任务就会导致界面卡顿,这类任务通常分为3类:排版,绘制,UI对象操作。

排版通常包括计算视图大小、计算文本高度、重新计算子式图的排版等操作。

绘制一般有文本绘制 (例如 CoreText)、图片绘制 (例如预先解压)、元素绘制 (Quartz)等操作。

UI对象操作通常包括 UIView/CALayer 等 UI 对象的创建、设置属性和销毁。

其中前两类操作可以通过各种方法扔到后台线程执行,而最后一类操作只能在主线程完成,并且有时后面的操作需要依赖前面操作的结果 (例如TextView创建时可能需要提前计算出文本的大小)。ASDK 所做的,就是尽量将能放入后台的任务放入后台,不能的则尽量推迟 (例如视图的创建、属性的调整)。

为此,ASDK 创建了一个名为 ASDisplayNode 的对象,并在内部封装了 UIView/CALayer,它具有和 UIView/CALayer 相似的属性,例如 frame、backgroundColor等。所有这些属性都可以在后台线程更改,开发者可以只通过 Node 来操作其内部的 UIView/CALayer,这样就可以将排版和绘制放入了后台线程。但是无论怎么操作,这些属性总需要在某个时刻同步到主线程的 UIView/CALayer 去。

ASDK 仿照 QuartzCore/UIKit 框架的模式,实现了一套类似的界面更新的机制:即在主线程的 RunLoop 中添加一个 Observer,监听了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。

具体的代码可以看这里:_ASAsyncTransactionGroup。

利用runloop监听主线程卡顿:

其中UI主要集中在__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);

和__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);之前。

获取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的状态就可以知道是否有卡顿的情况。

kCFRunLoopBeforeSources  持续处于这个状态,表明再处理事情

kCFRunLoopBeforeWaiting  表明处理完成 

 kCFRunLoopAfterWaiting  即将醒来处理事情 

 // 创建信号

    semaphore = dispatch_semaphore_create(0);

    NSLog(@"dispatch_semaphore_create:%@",[BGPerformanceMonitor getCurTime]);

    // 注册RunLoop状态观察

    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};

    //创建Run loop observer对象

    //第一个参数用于分配observer对象的内存

    //第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释

    //第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行

    //第四个参数用于设置该observer的优先级

    //第五个参数用于设置该observer的回调函数

    //第六个参数用于设置该observer的运行环境

    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,

                                       kCFRunLoopAllActivities,

                                       YES,

                                       0,

                                       &runLoopObserverCallBack,

                                       &context);

    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

    // 在子线程监控时长

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        while (YES) {   // 有信号的话 就查询当前runloop的状态

            // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)

            // 因为下面 runloop 状态改变回调方法runLoopObserverCallBack中会将信号量递增 1,所以每次 runloop 状态改变后,下面的语句都会执行一次

            // dispatch_semaphore_wait:Returns zero on success, or non-zero if the timeout occurred.

            long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));

            NSLog(@"dispatch_semaphore_wait:st=%ld,time:%@",st,[self getCurTime]);

            if (st != 0) {  // 信号量超时了 - 即 runloop 的状态长时间没有发生变更,长期处于某一个状态下

                if (!observer) {

                    timeoutCount = 0;

                    semaphore = 0;

                    activity = 0;

                    return;

                }

                NSLog(@"st = %ld,activity = %lu,timeoutCount = %d,time:%@",st,activity,timeoutCount,[self getCurTime]);

                // kCFRunLoopBeforeSources - 即将处理source kCFRunLoopAfterWaiting - 刚从休眠中唤醒

                // 获取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的状态就可以知道是否有卡顿的情况。

                // kCFRunLoopBeforeSources:停留在这个状态,表示在做很多事情

                if (activity == kCFRunLoopBeforeSources || activity == kCFRunLoopAfterWaiting) {    // 发生卡顿,记录卡顿次数

                    if (++timeoutCount < 5) {

                        continue;   // 不足 5 次,直接 continue 当次循环,不将timeoutCount置为0

                    }

                     // 收集Crash信息也可用于实时获取各线程的调用堆栈

                    PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];

                     PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];

                     NSData *data = [crashReporter generateLiveReport];

                    PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];

                    NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS];

                     NSLog(@"---------卡顿信息\n%@\n--------------",report);

                }

            }

            NSLog(@"dispatch_semaphore_wait timeoutCount = 0,time:%@",[self getCurTime]);

            timeoutCount = 0;

        }

    });

}

除此之外,还有tableviewcell的大图加载。。放个监听器监听runloop执行状态。。。一次加载一个图片。。。

参考https://github.com/edsum/RunLoop

你可能感兴趣的:(Runloop)