iOS之RunLoop,iosrunloop

來源:互聯網
上載者:User

iOS之RunLoop,iosrunloop

RunLoop是iOS線程相關的比較重要的一個概念,無論是主線程還是子線程,都對應一個RunLoop,如果沒有RunLoop,線程會馬上被系統回收。

本文主要CFRunLoop的源碼解析,並簡單闡述一下CFRunLoop的原理。

CFRunLoop是開源的,開源地址在:http://opensource.apple.com/tarballs/CF/ 

先看一張圖,這是主線程的RunLoop調用函數:

我們找到相應的CFRunLoop源碼:

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

可以看到,系統建立了一個do while迴圈,當狀態在stop或者finished時,就會退出迴圈,RunLoop會結束,線程會被回收。

註:1.0e10,這個表示1.0乘以10的10次方,這個參數主要是規定RunLoop的時間,傳這個時間,表示線程常駐。

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */    CHECK_FOR_FORK();    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;    __CFRunLoopLock(rl);    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;    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);    __CFRunLoopModeUnlock(currentMode);    __CFRunLoopPopPerRunData(rl, previousPerRun);    rl->_currentMode = previousMode;    __CFRunLoopUnlock(rl);    return result;}

先執行:__CFRunLoopFindMode,尋找是否有Mode

看代碼標註:

/* call with rl locked, returns mode locked */static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {    CHECK_FOR_FORK();    CFRunLoopModeRef rlm;    struct __CFRunLoopMode srlm;    memset(&srlm, 0, sizeof(srlm));    _CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);    srlm._name = modeName;    rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);    if (NULL != rlm) {  //如果有則返回        __CFRunLoopModeLock(rlm);        return rlm;    }    if (!create) {    //情況2        return NULL;    }    //情況3    rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);    if (NULL == rlm) {        return NULL;    }....//後面為建立一個Mode並賦初始值
__CFRunLoopMode是什麼

__CFRunLoopMode我自己理解為一種運行類型,它表示了當前線程運行在哪種類型下,會被哪種類型的事件喚醒。就好比你在程式中設定一個定時器Timer,運行在DefaultMode下,但是如果你滑動UIScrollview,系統會將當前線程的Mode改為UITrackingRunLoopMode,這時你的Timer就不會得到調用,因為當前線程的Mode和你Timer的Mode不同。當然,如果你想無論在哪種Mode下,Timer都想得到調用的話,你需要將Mode設定為CommonMode。

那麼,為什麼要這樣設計呢?我理解為這樣設計更為靈活。你可以指定事件需要在當前RunLoop是什麼Mode的時候被調用,跟上面舉的例子一樣。

系統提供了對應於__CFRunLoopMode的五種類型到NSRunloopMode中:

我們經常在代碼中使用的是NSDefaultRunLoopMode和NSRunLoopCommonModes。你也可以使用自訂的Mode。

__CFRunLoopMode包含了什麼及其作用

__CFRunLoopMode的結構體中包含了:Source0,Source1,Observers,Timers,Ports等。

Source0:處理App內部事件,如UIEvent、CFSocket,對應(CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION)這個函數。

Source1:由RunLoop和核心管理,Mach port驅動,如CFMachPort、CFMessagePort。對應(CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION)這個函數。

Observers:主要負責修改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};

Timers:負責讓App響應NSTimers,延遲的perform事件,對應(CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION)這個函數。我們可以在viewDidLoad裡面加入如下代碼:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];[timer fire];- (void)test{    NSLog(@"abc");}

可以得到如:

這裡簡單介紹下DoTimers和DoTimer兩個函數。DoTimers是一個for迴圈,在for迴圈裡面調用DoTimer。然後調用CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION,這個函數代碼很簡單,就是調用NSTimer或者Perform的回調。

Ports:port是用來做進程或者線程間通訊的,分為CFMachPort, CFMessagePort, CFSocketPort,詳細介紹可以看 這篇文章

再接著代碼往下看:

volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);

_per_run_data是用來描述當前CFRunLoop的狀態的,有三種狀態:初始狀態,wake,stop三種。注意到 volatile 這個關鍵字,它的意思是告訴編譯器不要最佳化這個變數,要每次都從記憶體中讀取該變數。

接著往下:

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

通知觀察者開始進入RunLoop。主要是通過(CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION)這個函數來完成。

接下來就要進入__CFRunLoopRun這個核心函數了。這個函數比較複雜,跟port相關的就忽略不講了。

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

這一段邏輯比較清晰,使用GCD建立一個定時任務,在指定的時間內,使用__CFRunLoopTimeOut喚醒RunLoop。因為使用了DISPATCH_TIME_FOREVER,所以__CFRunLoopTimeOut只會調用一次。

__CFRunLoopDoBlocks(rl, rlm); //執行RunLoop中的blockBoolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);if (sourceHandledThisLoop) {   __CFRunLoopDoBlocks(rl, rlm);}//執行Mode裡面的Source0的事件,如perform,uievent等//不知道為什麼__CFRunLoopDoBlocks要執行兩次

後續會有兩次狀態的改變:kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting,接著就是跟port相關的了,就不寫了。

最後一段:

if (sourceHandledThisLoop && stopAfterHandle) {    // 進入loop時參數說處理完事件就返回。    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)) {    // source/timer/observer一個都沒有了    retVal = kCFRunLoopRunFinished;}

 總體來講,RunLoop比較基礎但是也是比較複雜,在閱讀源碼的過程中也遇到不少疑惑,有些疑惑可能需要在後續的研究中才能慢慢發現答案。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.