Object-C Runloop詳解

一.Runloop介紹

1.什麼是Runloop

字面意思運行循環,它是一個對象,這個對象提供一個入口函數。 程序會進入do...while循環,處理事件。它不是一個普通的do-while循環,普通的do-while會一直暫用CPU資源,runloop在沒有消息處理時,會進入休眠表面資源佔用。ios

2.Runloop做用

  • 保持程序的持續運行
  • 處理app中的各類事件:觸摸、定時器、performSelector等
  • 節省cpu資源、提供程序的性能

3.Runloop和線程關係

  • 蘋果不容許直接建立 RunLoop,它只提供了兩個自動獲取的函數: CFRunLoopGetMain():獲取主運行循環。 CFRunLoopGetCurrent():獲取當前運行循環。
  • runloop和線程一一對應的關係.
  • 只能在當前線程中操做當前線程的RunLoop,而不能去操做其餘線程的RunLoop。
  • RunLoop對象在第一次獲取RunLoop時建立,銷燬則是在線程結束的時候。
  • 主線程的RunLoop對象系統自動幫助咱們建立好了,而子線程的RunLoop對象須要咱們主動獲取,由於子線程剛建立時並無RunLoop,若是你不主動獲取,那它一直都不會Yo有。

相關源碼分析: 從獲取線程RunLoop的方法CFRunLoopGetCurrent()進去:macos

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是一一對應的關係.

二.Runloop結構

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,讓它們之間互不影響。bash

RunLoop下共有五種mode:app

  • 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 的狀態,主要有如下幾種狀態。async

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關係

3、RunLoop邏輯流程源碼探索

Runloop的運行從CFRunLoopRun開始.ide

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);
複製代碼

總結:

  • 一、通知觀察者 RunLoop 已經啓動。
  • 二、通知觀察者即將要開始定時器。
  • 三、通知觀察者任何即將啓動的非基於端口的源。
  • 四、啓動任何準備好的非基於端口的源(Source0)。
  • 五、若是基於端口的源(Source1)準備好並處於等待狀態,進入步驟9。
  • 六、通知觀察者線程進入休眠狀態。
  • 七、將線程置於休眠狀態,知道下面的任一事件發生才喚醒線程。 某一事件到達基於端口的源 定時器啓動。 RunLoop 設置的時間已經超時。 RunLoop 被喚醒。
  • 八、通知觀察者線程將被喚醒。
  • 九、處理未處理的事件。 若是用戶定義的定時器啓動,處理定時器事件並重啓RunLoop。進入步驟2。 若是輸入源啓動,傳遞相應的消息。 若是RunLoop被顯示喚醒並且時間還沒超時,重啓RunLoop。進入步驟2
  • 十、通知觀察者RunLoop結束。

四.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();
      ```
    複製代碼

  • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"hello word"); });

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION

    延遲的perform, 延遲dispatch調用

    [self performSelector:@selector(fire) withObject:nil afterDelay:1.0];
    複製代碼

  • 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) 花費的時間是否過長。若是花費的時間大於某一個闕值,則認爲卡頓,此時能夠輸出對應的堆棧調用信息。

相關文章
相關標籤/搜索