美文网首页
Object-C Runloop详解

Object-C Runloop详解

作者: king_jensen | 来源:发表于2020-03-31 14:24 被阅读0次

    一.Runloop介绍

    1.什么是Runloop

    字面意思运行循环,它是一个对象,这个对象提供一个入口函数。
    程序会进入do...while循环,处理事件。它不是一个普通的do-while循环,普通的do-while会一直暂用CPU资源,runloop在没有消息处理时,会进入休眠表面资源占用。

    2.Runloop作用

    • 保持程序的持续运行
    • 处理app中的各种事件:触摸、定时器、performSelector等
    • 节省cpu资源、提供程序的性能

    3.Runloop和线程关系

    • 苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:
      CFRunLoopGetMain():获取主运行循环。
      CFRunLoopGetCurrent():获取当前运行循环。
    • runloop和线程一一对应的关系.
    • 只能在当前线程中操作当前线程的RunLoop,而不能去操作其他线程的RunLoop。
    • RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候。
    • 主线程的RunLoop对象系统自动帮助我们创建好了,而子线程的RunLoop对象需要我们主动获取,因为子线程刚创建时并没有RunLoop,如果你不主动获取,那它一直都不会Yo有。

    相关源码分析:
    从获取线程RunLoop的方法CFRunLoopGetCurrent()进去:

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

    获取RunLoop,调用_CFRunLoopGet0,当前线程(pthread_self()作为参数传入。

    static pthread_t kNilPthreadT = { nil, nil };
    
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
     //1.为Nil,设置为主线程
        if (pthread_equal(t, kNilPthreadT)) {
            t = pthread_main_thread_np();
        }
        2.加锁,保证线程安全
        __CFSpinLock(&loopsLock);
        3.__CFRunLoops是CFMutableDictionaryRef类型的静态全局变量,保存线程和runloop一一对应的关系
        if (!__CFRunLoops) {
        4.如果__CFRunLoops为空
            __CFSpinUnlock(&loopsLock);
            //5.创建可变字典CFMutableDictionaryRef
            CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            
         //6.通过pthread_main_thread_np()创建一个CFRunLoopRef
            CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
           //7.通过key-value的方式,将pthread_main_thread_np()和mainLoop存入`dict`
            CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
            //8.将dict赋值给__CFRunLoops
            if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
                CFRelease(dict);
            }
            CFRelease(mainLoop);
            __CFSpinLock(&loopsLock);
        }
        //9.在__CFRunLoops,线程作为key获取runloop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFSpinUnlock(&loopsLock);
        //10.不存在runloop
        if (!loop) {
        //11.创建一个loop
            CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFSpinLock(&loopsLock);
            loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
            if (!loop) {
            //12.将创建好的newloop存储到__CFRunLoops
                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;
    }
    
    • CFMutableDictionaryRef类型的全局静态变量__CFRunLoops,线程为key,对应的runloopvalue保存在__CFRunLoops,线程和runloop是一一对应的关系.
    image

    二.Runloop结构

    image

    RunLoop 相关的主要涉及五个类,如上图所示:

    • CFRunLoopRef
      • CFRunLoopModeRef//运行模式
        • CFRunLoopSourceRef
        • CFRunLoopTimerRef
        • CFRunLoopObserverRef

    1.CFRunLoopRef: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;//commonModes下的两个mode(kCFRunloopDefaultMode和UITrackingMode)
        CFMutableSetRef _commonModeItems;// 在commonModes状态下运行的对象(例如Timer)
        CFRunLoopModeRef _currentMode;////在当前loop下运行的mode
        CFMutableSetRef _modes;// // 运行的所有模式(CFRunloopModeRef类)
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFTypeRef _counterpart;
    };
    

    2.CFRunLoopModeRef:运行模式

    struct __CFRunLoopMode {
        CFStringRef _name;            // Mode Name
        CFMutableSetRef _sources0;    // Set
        CFMutableSetRef _sources1;    // Set
        CFMutableArrayRef _observers; // Array
        CFMutableArrayRef _timers;    // Array
        ...
    };
    

    一个RunLoop 对象中可能包含多个Mode,且每次调用 RunLoop 的主函数时,只能指定其中一个 Mode(CurrentMode)。可重写指定并切换Mode。主要是为了分隔开不同的 Source、Timer、Observer,让它们之间互不影响。

    RunLoop下共有五种mode:

    • kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行
    • UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
    • UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
    • GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
    • kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,实际是kCFRunLoopDefaultMode 和 UITrackingRunLoopMode的结合。

    项目中,如下如下场景:页面中有一个无限循环的banner,当用户在界面上滑动时,banner定时器不起作用。
    原因:主线程的 RunLoop 里有两个 Mode:kCFRunLoopDefaultModeUITrackingRunLoopMode。默认情况下是defaultMode,但是当滑动UIScrollView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会执行。如果想在滑动的时候不让定时器失效,可以使用CommonMode来解决。

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

    3.CFRunLoopSourceRef

    • Source0 :非基于 Port。只包含了一个回调(函数指针),不能主动触发事件。使用时,需先调用 CFRunLoopSourceSignal(source),将 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop)唤醒 RunLoop,让其处理这个事件。触摸事件处理和 performSelector:onThread: 都会触发 Source0 。
    • Source1:基于Port,通过内核和其他线程通信,接收、分发系统事件。 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。基于Port的线程间通信和系统事件捕捉都是 Source1 完成,当 Source1 捕捉到系统时间后,会放在队列中,之后再依次包装为 Source0 处理。

    4.CFRunLoopTimerRef

    CFRunLoopTimerRef 是定时源,你可以简单把它理解为NSTimer。其包含一个时间点和一个回调(函数指针)。当被加入到 RunLoop 时,RunLoop 会注册对应的时间点,当时间到时,RunLoop 会执行对应时间点的回调。NSTimer 和 performSelector:withObject:afterDelay: 都是通过其处理的。

    5.CFRunLoopObserverRef

    CFRunLoopObserverRef是观察者,主要用来监听RunLoop 的状态,主要有以下几种状态。

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),
        kCFRunLoopBeforeTimers = (1UL << 1),
        kCFRunLoopBeforeSources = (1UL << 2),
        kCFRunLoopBeforeWaiting = (1UL << 5),
        kCFRunLoopAfterWaiting = (1UL << 6),
        kCFRunLoopExit = (1UL << 7),
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    
    • kCFRunLoopEntry : 即将进入RunLoop
    • kCFRunLoopBeforeTimers :即将处理Timer
    • kCFRunLoopBeforeSources:即将处理Source
    • kCFRunLoopBeforeWaiting :即将进入休眠
    • kCFRunLoopAfterWaiting:即将从休眠中唤醒
    • kCFRunLoopExit :即将从RunLoop中退出
    • kCFRunLoopAllActivities:监听全部状态改变

    6.CFRunLoopRef,CFRunLoopModeRef,CFRunLoopSourceRef,CFRunLoopTimerRef,CFRunLoopObserverRef关系

    image

    三、RunLoop逻辑流程源码探索

    Runloop的运行从CFRunLoopRun开始.

    void CFRunLoopRun(void) {    /* DOES CALLOUT */
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    

    接下来都是调用CFRunLoopRunSpecific:

    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);
        //如果没找到 || mode中没有注册任何事件,则就此停止,不进入循环
        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);
        //取上一次运行的mode
        CFRunLoopModeRef previousMode = rl->_currentMode;
        //如果本次mode和上次的mode一致
        rl->_currentMode = currentMode;
        //初始化一个result为kCFRunLoopRunFinished
        int32_t result = kCFRunLoopRunFinished;
        
        if (currentMode->_observerMask & kCFRunLoopEntry )
            /// 1. 通知 Observers: RunLoop 即将进入 loop。
            __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        if (currentMode->_observerMask & kCFRunLoopExit )
            /// 10. 通知 Observers: RunLoop 即将退出。
            __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
        return result;
    }
    

    进入核心代码__CFRunLoopRun,代码太长,这里只贴出核心代码:

            /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            if (rlm->_observerMask & kCFRunLoopBeforeSources)
                /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            
            /// 执行被加入的block
            __CFRunLoopDoBlocks(rl, rlm);
            /// 4. RunLoop 触发 Source0 (非port) 回调。
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                /// 执行被加入的block
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            //如果没有Sources0事件处理 并且 没有超时,poll为false
            //如果有Sources0事件处理 或者 超时,poll都为true
            Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
            //第一次do..whil循环不会走该分支,因为didDispatchPortLastTime初始化是true
            if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                //从缓冲区读取消息
                msg = (mach_msg_header_t *)msg_buffer;
                /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
                if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                    //如果接收到了消息的话,前往第9步开始处理msg
                    goto handle_msg;
                }
    #elif DEPLOYMENT_TARGET_WINDOWS
                if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                    goto handle_msg;
                }
    #endif
            }
            
            didDispatchPortLastTime = false;
            /// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
            if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            //设置RunLoop为休眠状态
            __CFRunLoopSetSleeping(rl);
    
    
            msg = (mach_msg_header_t *)msg_buffer;
            /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
            /// • 一个基于 port 的Source 的事件。
            /// • 一个 Timer 到时间了
            /// • RunLoop 自身的超时时间到了
            /// • 被其他什么调用者手动唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
    
    
       /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
            if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    

    总结:

    • 1、通知观察者 RunLoop 已经启动。
    • 2、通知观察者即将要开始定时器。
    • 3、通知观察者任何即将启动的非基于端口的源。
    • 4、启动任何准备好的非基于端口的源(Source0)。
    • 5、如果基于端口的源(Source1)准备好并处于等待状态,进入步骤9。
    • 6、通知观察者线程进入休眠状态。
    • 7、将线程置于休眠状态,知道下面的任一事件发生才唤醒线程。
      某一事件到达基于端口的源
      定时器启动。
      RunLoop 设置的时间已经超时。
      RunLoop 被唤醒。
    • 8、通知观察者线程将被唤醒。
    • 9、处理未处理的事件。
      如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2。
      如果输入源启动,传递相应的消息。
      如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2
    • 10、通知观察者RunLoop结束。

    [图片上传失败...(image-cff93d-1585635835488)]

    四.runloop应用

    主线程几乎所有函数都从以下六个之一的函数调起:

    • CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

      用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimation

    • CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK

      消息通知、非延迟的perform、非延迟的dispatch调用、block回调、KVO

        block应用:
        ```
         void (^block)(void) = ^{
            NSLog(@"123");
         };
         block();
        ```
      
      image
    • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
      dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"hello word"); });

      image
    • CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION

      延迟的perform, 延迟dispatch调用

         [self performSelector:@selector(fire) withObject:nil afterDelay:1.0];
    
    image
    • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION

        处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用
      
    • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION

        由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort
      

    runloop与GCD

    • runLoop 的超时时间就是使用 GCD 中的 dispatch_source_t来实现的

    • 执行GCD MainQueue 上的异步任务

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

    runloop与自动释放池

    苹果在主线程 RunLoop里注册了两个 ``Observer:
    第一个Observer监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop),
    BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的池并创建新池;
    Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

    UI刷新

    当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayersetNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
    苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和Exit (即将退出Loop) 事件,回调去执行。遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

    事件响应

    苹果注册了一个 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 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

    如何处理timer

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

    meInterval tolerance API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0));
    

    NSTimer和performSEL方法实际上是对CFRunloopTimerRef的封装.

    如何处理performSelector

    当调用 NSObjectperformSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的RunLoop中。所以如果当前线程没有 RunLoop,则这个方法会失效。
    当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

    常驻子线程

    为了保证线程长期运转,可以在子线程中加入RunLoop,并且给Runloop设置item,防止Runloop自动退出。

    + (void)networkRequestThreadEntryPoint:(id)__unused object {
        @autoreleasepool {
            [[NSThread currentThread] setName:@"AFNetworking"];
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runLoop run];
        }
    }
    
    + (NSThread *)networkRequestThread {
        static NSThread *_networkRequestThread = nil;
        static dispatch_once_t oncePredicate;
        dispatch_once(&oncePredicate, ^{
            _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
            [_networkRequestThread start];
        });
        return _networkRequestThread;
    }
    - (void)start {
        [self.lock lock];
        if ([self isCancelled]) {
            [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        } else if ([self isReady]) {
            self.state = AFOperationExecutingState;
            [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        }
        [self.lock unlock];
    }
    

    卡顿监测

    所谓的卡顿一般是在主线程做了耗时操作,卡顿监测的主要原理是在主线程的RunLoop 中添加一个 observer,检测从 即将处理Source(kCFRunLoopBeforeSources) 到 即将进入休眠 (kCFRunLoopBeforeWaiting) 花费的时间是否过长。如果花费的时间大于某一个阙值,则认为卡顿,此时可以输出对应的堆栈调用信息。

    相关文章

      网友评论

          本文标题:Object-C Runloop详解

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