OC中Runloop的本質

我們在探究Runloop的本質前首先要知道什么是Runloop?

runloop定義:iOS程序中的運行循環(huán)機制,它能夠保證程序一直處于運行中狀態(tài)而不是執(zhí)行完任務后就立即退出

image

那么在項目的實際開發(fā)過程中阵具,我們又有哪些開發(fā)場景中使用到了runloop的循環(huán)機制尼碍遍?定铜,這里列舉runloop的常用場景如下:

  • 定時器
  • PerformSelector()
  • GCD Async
  • 所有的事件響應,手勢怕敬,列表滾動
  • 多線程
  • Autoreleasepool
  • ...

當我們新建一個iOS程序時揣炕,系統(tǒng)就會默認在主線程給我們創(chuàng)建了一個runloop對象,代碼如下:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    
    // 在UIApplicationMain函數內部东跪,系統(tǒng)會自動創(chuàng)建一個runloop對象畸陡,并添加到主線程中
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
image

當我們創(chuàng)建一個MacOs的命令行項目,系統(tǒng)沒有默認為我們創(chuàng)建runloop對象虽填,我們發(fā)現程序執(zhí)行完語句后丁恭,就會立即退出,代碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        NSLog(@"111");
    }
    
    NSLog(@"2222");
    return 0;
}

上面的代碼當執(zhí)行完NSLog(@"2222");打印后斋日,程序就直接退出了牲览。也就是說當前主線程沒有runloop時程序執(zhí)行完任務就直接退出,不能夠一直保持運行狀態(tài)恶守。

image

我們如何才能獲取到當前的runloop對象尼第献?

蘋果為開發(fā)者提供了2套框架來訪問和使用runloop對象

  • NSRunloop:Foundation框架API
  • CFRunLoopRef:Core Foundation框架API

其中NSRunloop是對CFRunLoopRef進行的一層更加面向對象的OC語法封裝

    // 獲取當前runloop
    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
    CFRunLoopRef currentRunLoop2 = CFRunLoopGetCurrent();
        
    // 獲取主線程runloop
    NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
    CFRunLoopRef mainRunLoop2 = CFRunLoopGetMain();
image
image

接下來我們來探究下runloop相關API的底層源碼,源碼查看路徑:CF框架 -> CFRunLoop.c文件 -> _CFRunLoopGet0

我們跟蹤下runloop的核心函數代碼流程如下:

// __CFRunLoops變量是CFMutableDictionaryRef類型的兔港,它就是一個全局的字典類型對象庸毫,用來存儲runloop和對應線程的集合
static CFMutableDictionaryRef __CFRunLoops = NULL;

static CFLock_t loopsLock = CFLockInit;

// 核心函數`_CFRunLoopGet0`,這個函數就是用來獲取runloop對象的

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    
    // 判斷線程是否為空
    if (pthread_equal(t, kNilPthreadT)) {
        // 如果線程為空衫樊,則將主線程作為傳遞進來的線程
        t = pthread_main_thread_np();
    }
    
    // 執(zhí)行加鎖操作
    __CFLock(&loopsLock);
    
    // 判斷__CFRunLoops集合是否有值
    if (!__CFRunLoops) {
        
        // __CFRunLoops集合沒有值飒赃,解鎖蜗帜,往集合中添加值
        __CFUnlock(&loopsLock);
        
        // 初始化__CFRunLoops字典對象
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        
        // 創(chuàng)建一個主線程的runloop:mainLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
        // 設置CFMutableDictionaryRef字典的值涣雕,主線程作為key,根據主線程創(chuàng)建出來的mainLoop作為value
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
        
    // 釋放mainLoop
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    
    // 根據參數線程`t`作為key难礼,去CFMutableDictionaryRef字典中查找對應的runloop對象
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    
    __CFUnlock(&loopsLock);
    
    if (!loop) {
        
        // 沒有找到對應的runloop臀栈,根據傳遞進來的線程`t`刚盈,創(chuàng)建一個新的runloop對象
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        
        // 加鎖操作
        __CFLock(&loopsLock);
        
        // 再次根據參數線程`t`去全局字典中獲取對應的runloop對象
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        
    if (!loop) {
        // 還是沒有找到對應的runloop
        // 就將剛剛新建的newLoop作為value,參數線程`t`作為key挂脑,添加到全局字典中
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        
        loop = newLoop;
    }
        
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
        
    CFRelease(newLoop);
    }
    
    // 判斷傳遞過來的線程是否為當前線程(pthread_self())
    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);
        }
    }
    
    // 返回runloop
    return loop;
}

通過上面函數_CFRunLoopGet0中的底層源碼實現藕漱,我們可以知道runloop和線程的關系,得出如下結論:

  • 每一個runloop對象中必定有一個與之對應的線程崭闲,因為程序中的所有runloop對象都保存在CFMutableDictionaryRef這個全局的字典集合中肋联,并且是以線程作為key,runloop對象作為value存儲在這個全局的集合中
  • 手動新創(chuàng)建的子線程刁俭,默認是沒有runloop的橄仍,從上面的底層源碼可以知道,runloop是在調用CFRunLoopRefAPI獲取runloop對象的時候創(chuàng)建的,通過判斷使用線程作為key在全局字典中取出對應的runloop侮繁,如果沒有取到對應的runloop就用傳遞的線程新創(chuàng)建一個runloop
image

接下來我們再來看看runloop的底層數據結構虑粥,源碼查找路徑:CF框架 -> CFRunLoop.c -> struct __CFRunLoop

__CFRunLoop結構體:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

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
    uint32_t _winthread;
    
    pthread_t _pthread; // 與之對應的線程
    
    // commonMode集合,存儲在_commonModes中的模式宪哩,都可以運行在kCFRunLoopCommonModes這種模式下
    CFMutableSetRef _commonModes;
    
    // 所有在commonModes這個模式下工作的`timer\source\observer等`都放到`_commonModeItems`集合中
    CFMutableSetRef _commonModeItems;
    
    CFRunLoopModeRef _currentMode; // runloop當前正在運行的Mode
    CFMutableSetRef _modes; // modes集合中存放的都是CFRunLoopModeRef對象
    
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

__CFRunLoop結構體中有一個CFMutableSetRef _modes成員娩贷,modes集合中又包含了多個CFRunLoopModeRef對象

__CFRunLoopMode結構體:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    
    Boolean _stopped;
    char _padding[3];
    
    CFStringRef _name; // Mode的名稱
    
    CFMutableSetRef _sources0; // _sources0集合中存放的都是CFRunLoopSourceRef
    CFMutableSetRef _sources1; // _sources1集合中存放的都是CFRunLoopSourceRef
    CFMutableArrayRef _observers; // _observers集合中存放的都是CFRunLoopObserverRef
    CFMutableArrayRef _timers; // _observers集合中存放的都是CFRunLoopTimerRef
    
    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 */
};

__CFRunLoopMode中又包含有_sources0_sources1锁孟、_observers彬祖、_timers這四個集合對象

__CFRunLoopSource結構體

typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;         /* immutable */
    
    CFMutableBagRef _runLoops;
    
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
    } _context;
};

__CFRunLoopObserver結構體

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    
    // runloop
    CFRunLoopRef _runLoop;
    
    CFIndex _rlCount;
    CFOptionFlags _activities;      /* immutable */
    CFIndex _order;         /* immutable */
    CFRunLoopObserverCallBack _callout; /* immutable */
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};

__CFRunLoopTimer結構體

typedef struct __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    
    // runloop
    CFRunLoopRef _runLoop;
    
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;       /* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

上面的這些runloop相關的類的對應關系如圖:

image
image

runloop的運行模式,最常用的就兩種模式:

  • kCFRunLoopDefaultMode
  • UITrackingRunLoopMode

我們在平時的開發(fā)過程中也有使用過kCFRunLoopCommonModes這種模式品抽,但是需要注意:

kCFRunLoopCommonModes并不是真正意義是上的mode储笑,它只是一個標記符,也就是說kCFRunLoopDefaultModeUITrackingRunLoopMode這兩種mode都被標記為common圆恤,存儲在CFMutableSetRef _commonModes集合中突倍,當我們設置runloop的模式為kCFRunLoopCommonModes時,系統(tǒng)就會在_commonModes這個集合中查找所有可以運行的模式來使用

image

我們從上面的CFRunLoopModeRef結構體成員中知道盆昙,runloop的modes集合中含有_sources0赘方、_sources1_observers弱左、_timers這四個,那么這些_sources0炕淮、_sources1拆火、_observers_timers到底有什么作用涂圆?

runloop在運行循環(huán)中不停的處理的任務就是這些_sources0们镜、_sources1_observers润歉、_timers

image

runloop中循環(huán)處理_observers模狭,也可以理解為runloop在運行循環(huán)中一直監(jiān)聽著_observers的以下這幾種狀態(tài)的變化

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

    kCFRunLoopEntry = (1UL << 0),          // 準備進入runloop
    kCFRunLoopBeforeTimers = (1UL << 1),   // 即將處理Timer事件
    kCFRunLoopBeforeSources = (1UL << 2),  // 即將處理Sources事件
    kCFRunLoopBeforeWaiting = (1UL << 5),  // 準備進入休眠狀態(tài)
    kCFRunLoopAfterWaiting = (1UL << 6),   // 即將從休眠狀態(tài)喚醒
    kCFRunLoopExit = (1UL << 7),               // 退出runloop狀態(tài)
    kCFRunLoopAllActivities = 0x0FFFFFFFU  // 所有的狀態(tài)
};
image

下面我們通過代碼來驗證下在runloop中手動添加observer,來觀察observer的狀態(tài)變化踩衩,代碼如下:

    // 創(chuàng)建一個Observe
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observerHandler, NULL);
    
    // 創(chuàng)建一個runloop
    CFRunLoopRef loop = CFRunLoopGetMain();
    
    // 將observere添加到runloop
    CFRunLoopAddObserver(loop, observer, kCFRunLoopDefaultMode);
    
    // 釋放observer
    CFRelease(observer);

observerHandler監(jiān)聽函數

void observerHandler(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
        break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
        break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
        break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
        break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
        break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
        break;
        default:
            break;
    }
}

我們通過打印可以看出嚼鹉,runloop確實是在各種observer的狀態(tài)間不停的切換

image

接下來我們再通過滾動列表示例,驗證runloop的mode的切換過程驱富,代碼如下:

    // 創(chuàng)建一個runloop
    CFRunLoopRef loop = CFRunLoopGetMain();
    
    // 創(chuàng)建一個Observe
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
            {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(loop);
                NSLog(@"kCFRunLoopEntry == %@", mode);
                CFRelease(mode);
            }
            break;
            case kCFRunLoopExit:
            {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(loop);
                NSLog(@"kCFRunLoopExit == %@", mode);
                CFRelease(mode);
            }
            break;
            default:
                break;
        }
    });
    
    // 將observere添加到runloop
    CFRunLoopAddObserver(loop, observer, kCFRunLoopCommonModes);
    
    // 釋放observer
    CFRelease(observer);

我們通過打印可以看到锚赤,當我們拖動列表時,runloop的modekCFRunLoopDefaultMode切換至UITrackingRunLoopMode

image

當我們停止列表拖動后褐鸥,runloop的mode又從UITrackingRunLoopMode切換至kCFRunLoopDefaultMode

image

接下來我們通過底層源碼來研究runloop的整個循環(huán)執(zhí)行過程线脚,底層源碼查找路徑:CF框架 -> CFRunLoop.c文件 -> CFRunLoopRunSpecific -> __CFRunLoopRun,底層核心源碼如下:

CFRunLoopRunSpecific函數核心代碼

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    CHECK_FOR_FORK();
        
    __CFRunLoopLock(rl);
    
    // 獲取當前的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;

    // 判斷是否進入runloop
    if (currentMode->_observerMask & kCFRunLoopEntry)
    
        // 通知observer,進入runloop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
        // __CFRunLoopRun:此函數中真正的開始處理runnloop中的任務
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    
    // 判斷是否退出runloop
    if (currentMode->_observerMask & kCFRunLoopExit )
    
        // 通知observer浑侥,退出runloop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
    
    return result;
}

__CFRunLoopRun函數核心代碼姊舵,此函數內代碼經過了優(yōu)化刪除,只保留了核心流程的關鍵代碼

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    
    // 此do-while循環(huán)就是runloop能夠保證程序一直運行而不退出的的核心
    do {
        
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
            // 通知observer寓落,處理timers
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        }
            
        if (rlm->_observerMask & kCFRunLoopBeforeSources) {
            // 通知observer括丁,處理Sources
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

            // 處理blocks
            __CFRunLoopDoBlocks(rl, rlm);
                        
            // 處理sources0
            __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                // 處理blocks
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            // 判斷是否有source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                // 如果有source1,則跳轉到`handle_msg`標記處零如,執(zhí)行標記后的代碼
                goto handle_msg;
            }
        }
            
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) {
            // 通知observer躏将,即將進入休眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            
            // 設置runloop開始休眠
            __CFRunLoopSetSleeping(rl);
        
            // runloop在此處就開始處于休眠狀態(tài),等待消息來喚醒runloop考蕾,使用內核機制來進行線程阻塞祸憋,而不是死循環(huán)
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            // runloop 取消休眠設置
            // user callouts now OK again
            __CFRunLoopUnsetSleeping(rl);
            
           // 通知observer,即將喚醒runloop
           __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        }
            
    // `handle_msg`標記
    handle_msg:;
        
        if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // 1肖卧、runloop被timer喚醒
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            
            // 處理timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        } else if (livePort == dispatchPort) {
            // 2蚯窥、runloop被dispatch喚醒
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            
            // 處理gcd相關事情
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            // 3、runloop被source喚醒
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            
            // 處理source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }

        // 處理blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 判斷retVal的值
        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;
}

我們在對上面的核心流程進行一個簡要的梳理總結:

  • 通知observer塞帐,進入runloop
  • 執(zhí)行__CFRunLoopRun函數
    1. 通知observer拦赠,處理timers
    2. 通知observer,處理sources
    3. 處理blocks
    4. 處理sources0
    5. 如果sourceHandledThisLoop條件滿足葵姥,處理blocks
    6. 判斷是否有sources1荷鼠,有則跳轉到handle_msg
    7. 通知observer,runloop即將進入休眠
    8. 通知observer榔幸,runloop即將結束休眠
      • 如果runloop被timer喚醒允乐,處理timers
      • 如果runloop被dispatch喚醒,處理gcd(dispatch_async(dispatch_get_main_queue(), ^{})
      • 如果runloop被source喚醒削咆,處理source1
    9. 處理blocks
    10. 判斷retVal的值牍疏,決定是跳到循環(huán)第一步還是退出runloop
  • 通知observer,退出runloop

上面runloop的循環(huán)執(zhí)行流程圖如下圖:

image

講解示例Demo地址:https://github.com/guangqiang-liu/08-Runloop

更多文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末鳞陨,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子瞻惋,更是在濱河造成了極大的恐慌厦滤,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歼狼,死亡現場離奇詭異馁害,居然都是意外死亡,警方通過查閱死者的電腦和手機蹂匹,發(fā)現死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門碘菜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事忍啸⊙鎏梗” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵计雌,是天一觀的道長悄晃。 經常有香客問我,道長凿滤,這世上最難降的妖魔是什么妈橄? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮翁脆,結果婚禮上眷蚓,老公的妹妹穿的比我還像新娘。我一直安慰自己反番,他們只是感情好沙热,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著罢缸,像睡著了一般篙贸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枫疆,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天爵川,我揣著相機與錄音,去河邊找鬼息楔。 笑死寝贡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的钞螟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼谎碍,長吁一口氣:“原來是場噩夢啊……” “哼鳞滨!你這毒婦竟也來了?” 一聲冷哼從身側響起蟆淀,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拯啦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后熔任,有當地人在樹林里發(fā)現了一具尸體褒链,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年疑苔,在試婚紗的時候發(fā)現自己被綠了甫匹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖兵迅,靈堂內的尸體忽然破棺而出抢韭,到底是詐尸還是另有隱情,我是刑警寧澤恍箭,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布刻恭,位于F島的核電站,受9級特大地震影響扯夭,放射性物質發(fā)生泄漏鳍贾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一交洗、第九天 我趴在偏房一處隱蔽的房頂上張望骑科。 院中可真熱鬧,春花似錦藕筋、人聲如沸纵散。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伍掀。三九已至,卻和暖如春暇藏,著一層夾襖步出監(jiān)牢的瞬間蜜笤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工盐碱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留把兔,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓瓮顽,卻偏偏與公主長得像县好,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暖混,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • RunLoop 是 iOS 和 OS X 開發(fā)中非陈乒保基礎的一個概念,這篇文章將從 CFRunLoop 的源碼入手拣播,...
    iOS_Alex閱讀 899評論 0 10
  • RunLoop 的概念 一般來講晾咪,一個線程一次只能執(zhí)行一個任務,執(zhí)行完成后線程就會退出贮配。如果我們需要一個機制谍倦,讓線...
    Mirsiter_魏閱讀 615評論 0 2
  • 轉自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_閱讀 1,335評論 0 5
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術 RunLoop 是 iOS 和 ...
    橙娃閱讀 847評論 1 2
  • RunLoop 是 iOS 和 OSX 開發(fā)中非忱崂眨基礎的一個概念昼蛀,這篇文章將從 CFRunLoop 的源碼入手宴猾,介...
    iOS大熊貓閱讀 201評論 0 0