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

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