iOS開發 - Runloop的mode是如何切換的

問題

  1. Runloop的mode是如何切換的?
  2. Runloop的mode切換時,上一個mode是需要退出嗎?

探索

Runloop的mode是如何切換的

UITableView滑動時,Runloop會進行切換mode,由kCFRunLoopDefaultMode切換爲UITrackingRunLoopMode,根據源碼,切換mode實際是調用CFRunLoopRunSpecific 這個函數

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

使用 lldb 來斷點這個函數,當滑動 UITableView

* thread #6, name = 'com.apple.uikit.eventfetch-thread', stop reason = breakpoint 4.1
  * frame #0: 0x00007fff23d9a7b0 CoreFoundation`CFRunLoopRunSpecific
    frame #1: 0x00007fff25939c71 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 211
    frame #2: 0x00007fff25939ee0 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 72
    frame #3: 0x00007fff48d39bfb UIKitCore`-[UIEventFetcher threadMain] + 138
    frame #4: 0x00007fff2594f9eb Foundation`__NSThread__start__ + 1047
    frame #5: 0x00007fff51c0c109 libsystem_pthread.dylib`_pthread_start + 148
    frame #6: 0x00007fff51c07b8b libsystem_pthread.dylib`thread_start + 15
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x00007fff23d9a7b0 CoreFoundation`CFRunLoopRunSpecific
    frame #1: 0x00007fff38ba6c1a GraphicsServices`GSEventRunModal + 139
    frame #2: 0x00007fff48c8b9ec UIKitCore`UIApplicationMain + 1605
    frame #3: 0x000000010506f8fa TestIsland`main(argc=1, argv=0x00007ffeeab8fd00) at main.m:18:12
    frame #4: 0x00007fff51a231fd libdyld.dylib`start + 1

mode切換時會退出嗎?

通過監聽runloop回調,來判斷mode切換時,是否會退出當前mode的循環,然後開啓一個新循環

    static CFRunLoopObserverRef observer;
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFOptionFlags activities = (kCFRunLoopEntry|kCFRunLoopBeforeWaiting | // before the run loop starts sleeping
                                kCFRunLoopExit);          // before exiting a runloop run
    
    observer = CFRunLoopObserverCreateWithHandler(NULL,        // allocator
                                                  activities,  // activities
                                                  YES,         // repeats
                                                  INT_MAX,     // order after CA transaction commits
                                                  ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        CFRunLoopRef rl = CFRunLoopGetCurrent();
        CFRunLoopMode mode = CFRunLoopCopyCurrentMode(rl);
        NSString *modestr = (__bridge NSString *)mode;
        if (activity == kCFRunLoopEntry) {
            printf("entry current mode is %s\n",modestr.UTF8String);
        }else if (activity == kCFRunLoopBeforeWaiting) {
            printf("beforewait current mode is %s\n",modestr.UTF8String);
        }else if (activity == kCFRunLoopExit){
            printf("exit current mode is %s\n",modestr.UTF8String);
        }
    });
    
    CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
    
    CFRelease(observer);

最終日誌如下:

beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
exit current mode is kCFRunLoopDefaultMode
entry current mode is UITrackingRunLoopMode
beforewait current mode is UITrackingRunLoopMode
beforewait current mode is UITrackingRunLoopMode
beforewait current mode is UITrackingRunLoopMode
...
beforewait current mode is UITrackingRunLoopMode
exit current mode is UITrackingRunLoopMode
entry current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode
beforewait current mode is kCFRunLoopDefaultMode

說明切換時,會先退出 kCFRunLoopDefaultMode 的runloop循環,再重新進入 UITrackingRunLoopMode 的runloop循環。

demo 鏈接: https://pan.baidu.com/s/1J0L7H1AbfRy–1Jjx7nEgg 密碼: 1cs9

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章