美文网首页
RunLoop浅析

RunLoop浅析

作者: 星空梦想 | 来源:发表于2019-11-02 11:46 被阅读0次

    ##RunLoop浅析

    参考文章:`https://www.jianshu.com/p/c38b5741919b`

    源码:`https://opensource.apple.com/tarballs/CF/`

        xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

    讲讲 RunLoop,项目中有用到吗?

    runloop内部实现逻辑?

    runloop和线程的关系?

    timer 与 runloop 的关系?

    程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?

    runloop 是怎么响应用户操作的, 具体流程是什么样的?

    说说runLoop的几种状态

    runloop的mode作用是什么?

    ###一 什么是runloop

    1.顾名思义

        运行循环

    2.应用范畴

        定时器(Timer)、PerformSelector

        GCD Async Main Queue

        事件响应、手势识别、界面刷新

        网络请求

        AutoreleasePool

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

    ###二 RunLoop的基本作用

        1.保持程序的持续运行

        2.处理App中的各种事件(比如触摸事件、定时器事件等)

        3.节省CPU资源,提高程序性能:该做事时做事,该休息时休息

        ......

    RunLoop在跑圈过程中,当接收到Input sources 或者 Timer sources时就会交给对应的处理方去处理。当没有事件消息传入的时候,RunLoop就休息了。

    ###三. RunLoop在哪里开启

    我们想象一个场景:为什么App程序启动之后能够持续运行在前台呢?

        int main(int argc, char * argv[]) {

            @autoreleasepool {

                return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

            }

        }

        从main函数中可以看出来 是因为UIApplicationMain方法的执行,那么UIApplicationMain内部做了些什么事能保持程序不退出呢?这就是RunLoop的功劳了。

    进入UIApplicationMain

        UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);

    我们知道主线程一开起来,就会跑一个和主线程对应的RunLoop,那么RunLoop一定是在程序的入口main函数中开启

    在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。

        UIApplicationMain的大致实现原理就是:(伪代码)

            int retVal = 0;

            do {

            int message = sleep_and_wait(); //睡眠中等待消息(比如详情点击各种事件)

            retVal = proess_message(message);//处理消息,更改返回值,如果为0,代表程序退出,不为0,程序持续运行。

            }while(retVal == 0);

    ###四. RunLoop的源码

        // 用DefaultMode启动

        void CFRunLoopRun(void) {  /* DOES CALLOUT */

            int32_t result;

            do {

            //kCFRunLoopDefaultMode,默认情况下,runLoop是在这个mode下运行的

                result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);

                CHECK_FOR_FORK();

            } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);

        }

            SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {    /* DOES CALLOUT */

        CHECK_FOR_FORK();

        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);

        }

    该方法,可以设置runLoop运行在哪个mode下modeName,超时时间seconds,以及是否处理完事件就返回returnAfterSourceHandled。

    这两个方法实际调用的是同一个方法CFRunLoopRunSpecific,其返回是一个SInt32类型的值,根据返回值,来决定runLoop的运行状况。

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

        CHECK_FOR_FORK();

        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;

        __CFRunLoopLock(rl);

        /// 首先根据modeName找到对应mode

        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;

          /// 通知 Observers: RunLoop 即将进入 loop

    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    /// 内部函数,进入loop,具体要做的事情

    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;

        }

    核心函数

        static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

        int32_t retVal = 0;

        do {

            /// 通知 Observers:RunLoop即将触发 Timer 回调。

            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

            ///  通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。

            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)

            /// 执行被加入的block

            __CFRunLoopDoBlocks(rl, rlm);

            ///  RunLoop 触发 Source0 (非port) 回调

            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

            /// 处理sources0返回为YES

            if (sourceHandledThisLoop) {

                /// 执行被加入的block

                __CFRunLoopDoBlocks(rl, rlm);

            }

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

            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {

                /// 处理消息

                goto handle_msg;

            }

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

            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

            __CFRunLoopSetSleeping(rl);

            /// 等待被唤醒

            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            // user callouts now OK again

            __CFRunLoopUnsetSleeping(rl);

            /// 通知 Observers: 被唤醒,结束休眠

            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:

            if (被Timer唤醒) {

                /// 处理Timers

                __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());

            } else if (被GCD唤醒) {

                /// 处理gcd

                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

            } else if (被Source1唤醒) {

                /// 被Source1唤醒,处理Source1

                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)

            }

            /// 处理block

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

        return retVal;

        }

        内部调用才是真正处理事件的函数,通过上面bt打印全部堆栈信息也可以得到验证。

        __CFRunLoopDoObservers 内部调用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

        __CFRunLoopDoBlocks 内部调用 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

        __CFRunLoopDoSources0 内部调用 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

        __CFRunLoopDoTimers 内部调用 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

        GCD 调用 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

        __CFRunLoopDoSource1 内部调用 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

    ###五. RunLoop对象

        Fundation框架 (基于CFRunLoopRef的封装)

        NSRunLoop对象

        CoreFoundation

        CFRunLoopRef对象

    如何获得RunLoop对象

        Foundation

        [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象

        [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

        Core Foundation

        CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象

        CFRunLoopGetMain(); // 获得主线程的RunLoop对象

    ###六. RunLoop和线程间的关系

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

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

        主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建

        RunLoop在第一次获取时创建,在线程结束时销毁

    通过源码查看上述对应

        // 拿到当前Runloop 调用_CFRunLoopGet0

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

            }

            __CFSpinLock(&loopsLock);

            if (!__CFRunLoops) {

                __CFSpinUnlock(&loopsLock);

                CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);

                / 根据传入的主线程获取主线程对应的RunLoop

                CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());

                // 保存主线程 将主线程-key和RunLoop-Value保存到字典中

                CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

                if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {

                    CFRelease(dict);

                }

                CFRelease(mainLoop);

                __CFSpinLock(&loopsLock);

            }

            // 从字典里面拿,将线程作为key从字典里获取一个loop

            CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

            __CFSpinUnlock(&loopsLock);

            // 如果loop为空,则创建一个新的loop,所以runloop会在第一次获取的时候创建

            if (!loop) {

                CFRunLoopRef newLoop = __CFRunLoopCreate(t);

                __CFSpinLock(&loopsLock);

                loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

                // 创建好之后,以线程为key runloop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runloop

                if (!loop) {

                    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);

                    loop = newLoop;

                }

                // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it

                __CFSpinUnlock(&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;

        }

        从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个 Dictionary 里。所以我们创建子线程RunLoop时,只需在子线程中获取当前线程的RunLoop对象即可[NSRunLoop currentRunLoop];如果不获取,那子线程就不会创建与之相关联的RunLoop,并且只能在一个线程的内部获取其 RunLoop

        [NSRunLoop currentRunLoop];方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。当线程结束时,RunLoop会被销毁。

        ###七. 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;

            };

    除一些记录性属性外,主要来看一下以下两个成员变量

            CFRunLoopModeRef _currentMode;

            CFMutableSetRef _modes;

    CFRunLoopModeRef其实是指向__CFRunLoopMode结构体的指针,__CFRunLoopMode结构体源码如下

        typedef struct __CFRunLoopMode *CFRunLoopModeRef; //RunLoop的运行模式

        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;

            CFMutableSetRef _sources1;

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

        };

    主要查看以下成员变量

            CFMutableSetRef _sources0;

            CFMutableSetRef _sources1;

            CFMutableArrayRef _observers;

            CFMutableArrayRef _timers;

    CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer,而RunLoop启动时只能选择其中一个Mode作为currentMode

    Source1/Source0/Timers/Observer分别代表什么

      1. Source1 : 基于Port的线程间通信

      2. Source0 : 触摸事件,PerformSelectors

    ###八. 详解RunLoop相关类及作用

      CFRunLoopRef - 获得当前RunLoop和主RunLoop

      CFRunLoopModeRef - RunLoop 运行模式,只能选择一种,在不同模式中做不同的操作

      CFRunLoopSourceRef - 事件源,输入源

      CFRunLoopTimerRef - 定时器时间

      CFRunLoopObserverRef - 观察者

    1. CFRunLoopModeRef

      CFRunLoopModeRef代表RunLoop的运行模式

      一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source、Timer、Observer

      每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode

      如果需要切换Mode,只能退出RunLoop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

    注意:一种Mode中可以有多个Source(事件源,输入源,基于端口事件源例键盘触摸等)

        Observer(观察者,观察当前RunLoop运行状态)和Timer(定时器事件源)。但是必须至少有一个Source或者Timer,因为如果Mode为空,RunLoop运行到空模式不会进行空转,就会立刻退出。

    ###九. 系统默认注册的5个Mode:

    ####1.RunLoop 有五种运行模式,其中常见的有1.2两种

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

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

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

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

    5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

    ####2.Mode间的切换

    我们平时在开发中一定遇到过,当我们使用NSTimer每一段时间执行一些事情时滑动UIScrollView,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况,我们通过一段代码来看一下

        -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

        {

            // [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];

            NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];

            // 加入到RunLoop中才可以运行

            // 1. 把定时器添加到RunLoop中,并且选择默认运行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode

            // [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

            // 当textFiled滑动的时候,timer失效,停止滑动时,timer恢复

            // 原因:当textFiled滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,因此timer失效,当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,因此timer又会重新启动了

            // 2. 当我们将timer添加到UITrackingRunLoopMode模式中,此时只有我们在滑动textField时timer才会运行

            // [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

            // 3. 那个如何让timer在两个模式下都可以运行呢?

            // 3.1 在两个模式下都添加timer 是可以的,但是timer添加了两次,并不是同一个timer

            // 3.2 使用站位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的都可以运行,下面两种模式被打上标签

            //0 : <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"}

            //2 : <CFString 0x10a8e85e0 [0x10a8c7a40]>{contents = "kCFRunLoopDefaultMode"}

            // 因此也就是说如果我们使用NSRunLoopCommonModes,timer可以在UITrackingRunLoopMode,kCFRunLoopDefaultMode两种模式下运行

            [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

            NSLog(@"%@",[NSRunLoop mainRunLoop]);

        }

        -(void)show

        {

            NSLog(@"-------");

        }

    由上述代码可以看出,NSTimer不管用是因为Mode的切换,因为如果我们在主线程使用定时器,此时RunLoop的Mode为kCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时我们滑动ScrollView时,RunLoop的Mode会切换到UITrackingRunLoopMode,因此在主线程的定时器就不在管用了,调用的方法也就不再执行了,当我们停止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,所以NSTimer就又管用了

      使用GCD也可是创建计时器,而且更为精确我们来看一下代码

    ####3. CFRunLoopSourceRef事件源(输入源)

        Source分为两种

            Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕)

            Source1:基于Port的 通过内核和其他线程相互发送消息(与内核相关)

    ####4. CFRunLoopObserverRef

    CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

            -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

            {

                //创建监听者

                /*

                第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配

                第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态

                第三个参数 Boolean repeats:YES:持续监听 NO:不持续

                第四个参数 CFIndex order:优先级,一般填0即可

                第五个参数 :回调 两个参数observer:监听者 activity:监听的事件

                */

                /*

                所有事件

                typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

                kCFRunLoopEntry = (1UL << 0),  //  即将进入RunLoop

                kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer

                kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source

                kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠

                kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒

                kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop

                kCFRunLoopAllActivities = 0x0FFFFFFFU

                };

                */

                CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

                switch (activity) {

                    case kCFRunLoopEntry:

                        NSLog(@"RunLoop进入");

                    break;

                    case kCFRunLoopBeforeTimers:

                        NSLog(@"RunLoop要处理Timers了");

                    break;

                    case kCFRunLoopBeforeSources:

                        NSLog(@"RunLoop要处理Sources了");

                    break;

                    case kCFRunLoopBeforeWaiting:

                        NSLog(@"RunLoop要休息了");

                    break;

                    case kCFRunLoopAfterWaiting:

                        NSLog(@"RunLoop醒来了");

                    break;

                    case kCFRunLoopExit:

                        NSLog(@"RunLoop退出了");

                    break;

                default:

                break;

                }

            });

            // 给RunLoop添加监听者

            /*

            第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop

            第二个参数 CFRunLoopObserverRef observer 监听者

            第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态

            */

            CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

            /*

            CF的内存管理(Core Foundation)

            凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release

            GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了

            */

            CFRelease(observer);

        }

    Observer确实用来监听RunLoop的状态,包括唤醒,休息,以及处理各种事件

    ###十. RunLoop处理逻辑流程图

    网址 `https://www.jianshu.com/p/de752066d0ad`

    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

    ###十一. RunLoop应用

    线程保活网址 `https://juejin.im/post/5c8cba495188257e16048237`

    ###App启动优化

    https://juejin.im/post/5cff0ada6fb9a07edc0b4c3c

    相关文章

      网友评论

          本文标题:RunLoop浅析

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