底層原理探究(二)RunLoop

轉(zhuǎn)自: 老司機(jī)出品——源碼解析之RunLoop詳解
入門使用: RunLoop入門 看我就夠了
孫源的Runloop視頻筆記

以下2篇還沒有看
拓展:
深入理解RunLoop
iOS底層原理總結(jié) - RunLoop

開始
NSRunLoop是基于CoreFoundation框架中的CFRunLoop進(jìn)行的一層簡單的封裝,所以我們這里著重介紹CFRunLoop
1.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;
    CFTypeRef _counterpart;
};

CFRunLoop是這么一個結(jié)構(gòu)體蚌本。
_lock 結(jié)構(gòu)體用來保證線程安全的鎖 ,
_wakeUpPort 用來喚醒runLoop的端口
_pthread 線程對象 钠署,
_modes 一個模式集合
以及一些其他輔助的屬性。

1.1 _pthread
runLoop與線程是一一對應(yīng)的荒椭。也就是一個runLoop對應(yīng)著一個線程谐鼎,一個線程對應(yīng)著一個runLoop。
我們從runLoop的構(gòu)造函數(shù)和獲取函數(shù)即可看出:

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, 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;
}

可以看出構(gòu)造一個runLoop對象僅需要一個pthread_t線程即可。即一個runLoop對應(yīng)一個線程狸棍。

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        //如果傳入線程為空指針則默認(rèn)取主線程對應(yīng)的runLoop
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        //__CFRunLoops就是一個全局字典身害,以下代碼為如果全局字典不存在則創(chuàng)建全局字典,并將主線程對應(yīng)的mainLoop存入字典中
        __CFSpinUnlock(&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);
        __CFSpinLock(&loopsLock);
    }
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //從全局字典中草戈,取出對應(yīng)線程的runLoop
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        //若對應(yīng)線程的runLoop為空塌鸯,則創(chuàng)建對應(yīng)相乘的runLoop并保存在全局字典中
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&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
        __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的獲取函數(shù),我們看到系統(tǒng)從一個全局字典中取出runLoop唐片,key就是一個線程丙猬,這足以說明runLoop與線程是一一對應(yīng)的關(guān)系。

值得一提的是费韭,一個線程最開始是沒有對應(yīng)的runLoop的茧球,是在調(diào)用獲取函數(shù)的時候才對應(yīng)了一個runLoop的。
因為本身這個對應(yīng)關(guān)系是由runLoop類管理的星持,而不是線程抢埋。

當(dāng)然上述兩個為私有api,CFRunLoop真正對外暴露的只有兩個接口:

CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);

兩個方法的實現(xiàn)很簡單督暂,只要把對應(yīng)的線程傳入獲取函數(shù)即可:

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

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

1.2 _modes
我們看到揪垄,一個runLoop中同時還維護(hù)著一個集合,_modes逻翁。那么這個modes是做什么的呢饥努?應(yīng)該說,_modes才是runLoop的核心八回。

首先我們看一下這個_modes里面到底都裝了些什么酷愧?
答案是__CFRunLoopMode對象。那么他又是什么呢辽社?

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

這里挑出幾個重點
有用來標(biāo)志runLoopMode的標(biāo)志_name
有兩個事件源的集合_sources0、_sources1
有一組觀察者_obeserver
有一組被加入到runLoop中的_timers
還有Mode本身維護(hù)著的一個用于計時 _timerSource翘鸭,_timerPort
這兩個一個是GCD時鐘滴铅、一個是內(nèi)核時鐘。

下面runLoop的實現(xiàn)中結(jié)合代碼講為什么runLoopMode長這樣就乓。
2. RunLoop代碼實現(xiàn)
接下來代碼有點長汉匙,先看一下大概流程,然后對著流程去看一下代碼生蚁。

圖.png

RunLoop核心代碼
300行代碼=噩翠。= 有點長

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();//獲取當(dāng)前內(nèi)核時間
    
    if (__CFRunLoopIsStopped(rl)) {
        //如果當(dāng)前runLoop或者runLoopMode為停止?fàn)顟B(tài)的話直接返回
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    //判斷是否是第一次在主線程中啟動RunLoop,如果是且當(dāng)前RunLoop為主線程的RunLoop,那么就給分發(fā)一個隊列調(diào)度端口
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();

#if USE_DISPATCH_SOURCE_FOR_TIMERS
    //給當(dāng)前模式分發(fā)隊列端口
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if (!modeQueuePort) {
            CRASH("Unable to get port for run loop mode queue (%d)", -1);
        }
    }
#endif
    //初始化一個GCD計時器邦投,用于管理當(dāng)前模式的超時
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
        
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
        timeout_context->ds = timeout_timer;
        timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
        dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
    } else {
        // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    // 第一步伤锚,進(jìn)入循環(huán)
    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
        HANDLE livePort = NULL;
        Boolean windowsMessageReceived = false;
#endif
        __CFPortSet waitSet = rlm->_portSet;
        
        //設(shè)置當(dāng)前循環(huán)監(jiān)聽端口的喚醒
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        // 第二步,通知觀察者準(zhǔn)備開始處理Timer源事件
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // 第三步志衣,通知觀察者準(zhǔn)備開始處理Source源事件
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        //執(zhí)行提交到runLoop中的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 第四步屯援,執(zhí)行source0中的源事件
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        //如果當(dāng)前source0源事件處理完成后執(zhí)行提交到runLoop中的block
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        //標(biāo)志是否等待端口喚醒
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        // 第五步猛们,檢測端口,如果端口有事件則跳轉(zhuǎn)至handle_msg(首次執(zhí)行不會進(jìn)入判斷狞洋,因為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;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                goto handle_msg;
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }
#endif
        }
        
        didDispatchPortLastTime = false;
        
        // 第六步弯淘,通知觀察者線程進(jìn)入休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        // 標(biāo)志當(dāng)前runLoop為休眠狀態(tài)
        __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);
        
        // 第七步,進(jìn)入循環(huán)開始不斷的讀取端口信息吉懊,如果端口有喚醒信息則喚醒當(dāng)前runLoop
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        do {
            if (kCFUseCollectableAllocator) {
                objc_clear_stack(0);
                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);
            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);
#else
        if (kCFUseCollectableAllocator) {
            objc_clear_stack(0);
            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);
#endif

#elif DEPLOYMENT_TARGET_WINDOWS
        // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
        __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        
        // 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);
        
        //標(biāo)志當(dāng)前runLoop為喚醒狀態(tài)
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        // 第八步庐橙,通知觀察者線程被喚醒了
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        //執(zhí)行端口的事件
    handle_msg:;
        
        //設(shè)置此時runLoop忽略端口喚醒(保證線程安全)
        __CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_TARGET_WINDOWS
        if (windowsMessageReceived) {
            // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            if (rlm->_msgPump) {
                rlm->_msgPump();
            } else {
                MSG msg;
                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            
            // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
            // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
            // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
            __CFRunLoopSetSleeping(rl);
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            __CFRunLoopUnsetSleeping(rl);
            // If we have a new live port then it will be handled below as normal
        }
#endif
        // 第九步,處理端口事件
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
            // Always reset the wake up port, or risk spinning forever
            ResetEvent(rl->_wakeUpPort);
#endif
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            //處理定時器事件
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
            // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        //處理有GCD提交到主線程喚醒的事件
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
            void *msg = 0;
#endif
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            //處理source1喚醒的事件
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                mach_msg_header_t *reply = NULL;
                // 處理Source1(基于端口的源)
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
#elif DEPLOYMENT_TARGET_WINDOWS
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
            }
        }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        __CFRunLoopDoBlocks(rl, rlm);
        
        //返回對應(yīng)的返回值并跳出循環(huán)
        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);
    
    // 第十步借嗽,釋放定時器
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    return retVal;
}

這300行的流程其實就是上面歸納的10步:

首先進(jìn)入runLoop對應(yīng)的Mode并開始循環(huán)态鳖,然后在休眠之前做了三件事:DoBlocks、DoSource0淹魄、檢測source1端口是否有消息郁惜,如果有則跳過稍后的休眠。
然后runLoop就進(jìn)入了休眠狀態(tài)甲锡,直到有端口事件喚醒runLoop兆蕉,被喚醒后則處理響應(yīng)的端口事件然后再次開始循環(huán)。直到runLoop超時或者runLoop被停止后在結(jié)束runLoop缤沦。

1.source0虎韵,source1
首先這個源事件分為兩種,一種是不基于端口的source0缸废,一直是基于端口的source1包蓝。

source0 只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件企量。使用時测萎,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理届巩,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop硅瞧,讓其處理這個事件。

source0 呢主要處理App內(nèi)部事件恕汇、App自己負(fù)責(zé)管理(觸發(fā))腕唧,如UIEvent、CFSocket瘾英,
說明了button的點擊是屬于sourse0的枣接。

source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針),被用于通過內(nèi)核和其他線程相互發(fā)送消息缺谴。這種 Source 能主動喚醒 RunLoop 的線程但惶,其原理在下面會講到。

source1 呢主要有Runloop和內(nèi)核管理,Mach port驅(qū)動榆骚,如CFMahPort片拍、CFMessagePort

2.NSTimer事件是借助runLoop實現(xiàn)的。

在初始化Timer的時候要將Timer提交到runLoop中妓肢,并且要指定mode捌省,才可以工作。今天我們可以深入講一下碉钠。

這個事件是怎么執(zhí)行的纲缓?并且為什么有的時候會延遲?為什么子線程中創(chuàng)建的Timer并不執(zhí)行喊废?

首先祝高,在進(jìn)入循環(huán)開始以后,就要處理source0事件污筷,處理后檢測一下source1端口是否有消息工闺,如果一個Timer的時間間隔剛好到了則此處有可能會得到一個消息,則runLoop直接跳轉(zhuǎn)至端口激活處從而去處理Timer事件瓣蛀。

第二陆蟆,為什么會延遲?我們知道惋增,兩次端口事件是在兩個runLoop循環(huán)中分別執(zhí)行的叠殷。比如Timer的時間間隔為1秒,在第一次Timer回調(diào)結(jié)束后诈皿,在很短時間內(nèi)立即進(jìn)入runLoop的下一次循環(huán)林束,這次并不是Timer回調(diào)并且是一個計算量非常大的任務(wù),計算時間超過了1秒稽亏,那么runLoop的第二個循環(huán)就要執(zhí)行很久壶冒,無法進(jìn)入下一個循環(huán)等待有可能即將到來的Timer第二次回調(diào)的信號,所以Timer第二次回調(diào)就會推遲了截歉。

第三胖腾,為什么在子線程中創(chuàng)建的Timer并且提交到當(dāng)前runLoop中并不會運行?這還是要從runLoop的獲取函數(shù)中看怎披,當(dāng)調(diào)用currentRunLoop的時候會取當(dāng)前線程對應(yīng)的runLoop胸嘁,而首次是取不到的瓶摆,則會創(chuàng)建一個新的runLoop凉逛。但是!這個runLoop并沒有run群井。就是沒有開啟=状飞。=

3.同一時間內(nèi),runLoop只能運行同一種mode。那commonMode是怎么實現(xiàn)的诬辈?
從runLoop的結(jié)構(gòu)我們可以知道酵使,一個runLoop會包含多種runLoopMode,runLoop是不停的在這些mode之間進(jìn)行切換去完成對應(yīng)Mode中的相關(guān)任務(wù)焙糟。

runLoop中多個mode.png

首先為什么說runLoop只能在各種Mode之間切換口渔,同一時間只能存在一個呢?
因為上面那個方法必須要傳一個runLoopMode穿撮,然后這個方法貫穿始終缺脉,都在用。

我們看到悦穿,上面的方法中首先就要傳入一個指定的mode才能執(zhí)行對應(yīng)mode中的事件攻礼。那么所謂的CommonMode是如何實現(xiàn)的呢?

我們看到runLoop中執(zhí)行任務(wù)有調(diào)到CFRunLoopDoBlocks這么一個函數(shù)栗柒,那么這個函數(shù)是什么樣的呢礁扮?

static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
    if (!rl->_blocks_head) return false;
    if (!rlm || !rlm->_name) return false;
    ...省略一些非重點...
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
    Boolean doit = false;
    if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
        doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }else {
        doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }
    if (!doit) prev = curr;
    if (doit) {
        if (prev) prev->_next = item;
        if (curr == head) head = item;
        if (curr == tail) tail = prev;
        void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
        if (doit) {
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
            did = true;
        }
        ...省略一些非重點...
        return did;
    }

我們看到doit這個bool變量完全決定了當(dāng)前block是否執(zhí)行。默認(rèn)他是No的瞬沦,而他被置為true的條件就是CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode))太伊。就是當(dāng)前mode與制定mode相等或者當(dāng)前mode為commonMode(此處為一個字符串)且commonMode(此處為一個集合,若有不懂蛙埂,請看runLoop結(jié)構(gòu))這個集合中包含指定mode倦畅。

這是因為這個判斷的存在才允許commondMode可以在任意Mode下執(zhí)行。
當(dāng)然這是提交到runLoop里的代碼塊才會走到__CFRunLoopDoBlocks這個方法绣的。

相同的叠赐,我們通過上述代碼也可以知道,runLoop通過端口喚醒的事件需要通過__CFRunLoopDoSource1和__CFRunLoopDoTimers兩個方法來調(diào)用屡江。__CFRunLoopDoSource1方法沒什么說的芭概,直接調(diào)用源事件runLoopSourceRef即可。重點我們看一下Timer的實現(xiàn)惩嘉,核心代碼如下:

static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    //遍歷runLoopMode維護(hù)的Timers數(shù)組罢洲,取其中有效的timer并加入新臨時數(shù)組
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    //遍歷臨時數(shù)組,每個有效Timer調(diào)用__CFRunLoopDoTimer
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

我們可以看到文黎,此處Timer是否會回調(diào)完全取決于對應(yīng)Mode的_Timers數(shù)組惹苗。那么當(dāng)我們將Timer加入到commonModes中的時候一定是同時將Timer加入到了commonModes所包含的其他Mode中了,我們看下代碼:

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {//commonModes分支
        //取到commonModes所代表的Mode的集合
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            //將commonModeItems中加入當(dāng)前定時器
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            //最主要還是還是這句耸峭,這句的作用是集合中的所有對象均調(diào)用__CFRunLoopAddItemToCommonModes這個方法桩蓉。
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {//非commonModes的分支
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
        }
        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
                rlt->_runLoop = rl;
            } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                return;
            }
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    __CFRunLoopUnlock(rl);
}

static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
        CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
        CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
        CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}

我們可以看到,當(dāng)加入到commonModes中時劳闹,實際上系統(tǒng)是找出commonModes代表的所有Mode院究,如defaultMode和trackingMode洽瞬,讓后分別將其加入了這些mode中。
同樣的方法還有CFRunLoopAddSource/CFRunLoopAddObserver都是同樣的道理业汰。

所以說當(dāng)scrollView或其子類進(jìn)行滾動的時候伙窃,UIKIT會自動將當(dāng)前runLoopMode切換為UITrackingRunLoopMode,所以你加在defaultMode中的計時器當(dāng)然不會走了样漆。

4.runLoop是如何休眠有如何被喚醒的为障?

從第7步開始,我們看到runLoop進(jìn)入了休眠狀態(tài)放祟。然而所謂的休眠狀態(tài)指示將當(dāng)前runLoop標(biāo)記為休眠之后产场,進(jìn)入了一個while死循環(huán)。然后在循環(huán)內(nèi)就不斷的去讀取端口消息舞竿。如果說從端口中讀取到一個喚醒信息的話京景,break掉while循環(huán)從而進(jìn)入喚醒狀態(tài)。

5.可以喚醒runLoop的都有哪些事件骗奖?
從源碼中我們可以看出确徙,所謂的runLoop進(jìn)入休眠狀態(tài)不過是一個while循環(huán),如下:

do {
    if (kCFUseCollectableAllocator) {
        objc_clear_stack(0);
        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);
    
    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);

相應(yīng)的我們還得看一個函數(shù)执桌,__CFRunLoopServiceMachPort:

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t**buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) { /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        ret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
        CFRUNLOOP_WAKEUP(ret);
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        } if (MACH_RCV_TOO_LARGE != ret) break;
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

我們先看后面這個函數(shù)鄙皇,在這里僅兩種情況會對livePort進(jìn)行賦值
一種是成功獲取到消息后仰挣,會根據(jù)情況賦值為msg->msgh_local_port或者M(jìn)ACH_PORT_NULL伴逸,
另一種獲取消息超時的情況會賦值為MACH_PORT_NULL。首先請先記住這兩個結(jié)論膘壶。

然后我們把目光聚焦到while循環(huán)中错蝴,在調(diào)用__CFRunLoopServiceMachPort后如果livePort變成了modeQueuePort(livePort初值為MACH_PORT_NULL),則代表為當(dāng)前隊列的檢測端口颓芭,那么在_dispatch_runloop_root_queue_perform_4CF的條件下再次進(jìn)入二級循環(huán)顷锰,知道Timer被激活了才跳出二級循環(huán)繼續(xù)循環(huán)一級循環(huán)。

那么如果livePort不為modeQueuePort時我們的runLoop被喚醒亡问。這代表__CFRunLoopServiceMachPort給出的livePort只有兩種可能:
一種情況為MACH_PORT_NULL官紫,另一種為真正獲取的消息的端口。

所以我們可以看到后面runLoop處理端口時間的方法如下的判斷:

if (MACH_PORT_NULL == livePort) {//什么都不做州藕,有肯能是超時之類的或者是信息過大
    CFRUNLOOP_WAKEUP_FOR_NOTHING();
    // handle nothing
} else if (livePort == rl->_wakeUpPort) {//只有外界調(diào)用CFRunLoopWakeUp才會進(jìn)入此分支束世,這是外部主動喚醒runLoop的接口
    CFRUNLOOP_WAKEUP_FOR_WAKEUP();
    // do nothing on Mac OS
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
    //這里不是從runLoop休眠后喚醒到這里的,而是在runLoop10步中的第五步跳轉(zhuǎn)過來的床玻,是處理計時器事件
    CFRUNLOOP_WAKEUP_FOR_TIMER();
    ...省略處理計時器事件的代碼...
}
#endif
else if (livePort == dispatchPort) {//這里是處理GCD提交到mainQueue的block的端口事件
    CFRUNLOOP_WAKEUP_FOR_DISPATCH();
    ...省略處理GCD的代碼...
} else {//之前所有情況都不是毁涉,那么喚醒runLoop的就只可能是source1的源事件了。
    CFRUNLOOP_WAKEUP_FOR_SOURCE();
    ...省略處理source1源事件的代碼...
    }
}

runLoop的喚醒過程笨枯,及喚醒過后的時間處理就是上面的流程薪丁,
大家可以看看每個分支后的注釋。同時runLoopRun的核心代碼也就解讀完畢了馅精。

剩下的幾個run方法事實上都是對這個核心方法的封裝了

CFRunLoopRunSpecific
CFRunLoopRun
CFRunLoopRunInMode

至此严嗜,整個runLoop中的核心流程分析了一遍~

runLoop都能做什么 說了這么多,那么runLoop都能做些什么呢?

AutoReleasePool:
App啟動后洲敢,蘋果在主線程 RunLoop 里注冊了兩個 Observer漫玄,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()。

第一個 Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop)压彭,其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池睦优。其 order 是-2147483647,優(yōu)先級最高壮不,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前汗盘。

第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池询一。這個 Observer 的 order 是 2147483647隐孽,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后健蕊。

在主線程執(zhí)行的代碼菱阵,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的缩功。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著晴及,所以不會出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建 Pool 了嫡锌。

CAAnimation
我們知道CAAniamtion為我們提供的是補(bǔ)間動畫虑稼,開發(fā)者只要給出始末狀態(tài)后中間狀態(tài)有系統(tǒng)自動生成。那么動畫是怎么出現(xiàn)的呢势木,是開發(fā)者給出始末狀態(tài)后动雹,系統(tǒng)計算出每一個中間態(tài)的各項參數(shù),然后啟一個定時器不斷去回調(diào)并改變屬性跟压。

事件響應(yīng)
蘋果注冊了一個 Source1 (基于 mach port 的) 用來接收系統(tǒng)事件胰蝠,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。

當(dāng)一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后震蒋,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收茸塞。這個過程的詳細(xì)情況可以參考這里。SpringBoard 只接收按鍵(鎖屏/靜音等)查剖,觸摸钾虐,加速,接近傳感器等幾種 Event笋庄,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進(jìn)程效扫。隨后蘋果注冊的那個 Source1 就會觸發(fā)回調(diào)倔监,并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。

_UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā)菌仁,其中包括識別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等浩习。通常事件比如 UIButton 點擊、touchesBegin/Move/End/Cancel 事件都是在這個回調(diào)中完成的济丘。

手勢識別
當(dāng)上面的 _UIApplicationHandleEventQueue() 識別了一個手勢時谱秽,其首先會調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理摹迷。

蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進(jìn)入休眠) 事件疟赊,這個Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會獲取所有剛被標(biāo)記為待處理的 GestureRecognizer峡碉,并執(zhí)行GestureRecognizer的回調(diào)近哟。

當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時,這個回調(diào)都會進(jìn)行相應(yīng)處理鲫寄。

定時器
PerformSelecter
當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后椅挣,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒有 RunLoop塔拳,則這個方法會失效鼠证。

當(dāng)調(diào)用 performSelector:onThread: 時,實際上其會創(chuàng)建一個 Timer 加到對應(yīng)的線程去靠抑,同樣的量九,如果對應(yīng)線程沒有 RunLoop 該方法也會失效。

完颂碧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荠列,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子载城,更是在濱河造成了極大的恐慌肌似,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诉瓦,死亡現(xiàn)場離奇詭異川队,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)睬澡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門固额,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人煞聪,你說我怎么就攤上這事斗躏。” “怎么了昔脯?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵啄糙,是天一觀的道長笛臣。 經(jīng)常有香客問我,道長隧饼,這世上最難降的妖魔是什么沈堡? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮桑李,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窿给。我一直安慰自己贵白,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布崩泡。 她就那樣靜靜地躺著禁荒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪角撞。 梳的紋絲不亂的頭發(fā)上呛伴,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音谒所,去河邊找鬼热康。 笑死,一個胖子當(dāng)著我的面吹牛劣领,可吹牛的內(nèi)容都是我干的姐军。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼尖淘,長吁一口氣:“原來是場噩夢啊……” “哼奕锌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起村生,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤惊暴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后趁桃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辽话,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年卫病,在試婚紗的時候發(fā)現(xiàn)自己被綠了屡穗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡忽肛,死狀恐怖村砂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屹逛,我是刑警寧澤础废,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布汛骂,位于F島的核電站,受9級特大地震影響评腺,放射性物質(zhì)發(fā)生泄漏帘瞭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一蒿讥、第九天 我趴在偏房一處隱蔽的房頂上張望蝶念。 院中可真熱鬧,春花似錦芋绸、人聲如沸媒殉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽廷蓉。三九已至,卻和暖如春马昙,著一層夾襖步出監(jiān)牢的瞬間桃犬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工行楞, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留攒暇,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓子房,卻偏偏與公主長得像扯饶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子池颈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1 Runloop機(jī)制原理 深入理解RunLoop http://www.cocoachina.com/ios/2...
    Kevin_Junbaozi閱讀 3,983評論 4 30
  • 一尾序、什么是runloop 字面意思是“消息循環(huán)、運行循環(huán)”躯砰。它不是線程每币,但它和線程息息相關(guān)。一般來講琢歇,一個線程一次...
    WeiHing閱讀 8,109評論 11 111
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,436評論 0 13
  • 前言 RunLoop是iOS和OSX開發(fā)中非忱嫉。基礎(chǔ)的一個概念,這篇文章將從CFRunLoop的源碼入手李茫,介紹Run...
    暮年古稀ZC閱讀 2,229評論 1 19
  • 這兩天感冒了揭保,要死要活的,就在想魄宏,身體健康真的很重要 秸侣,感冒尚且如此難受 ,要是得了別的什么病,讓人堅強(qiáng)堅持那真是...
    小胖潘閱讀 195評論 0 1