RunLoop原理

作者: NeroXie | 来源:发表于2018-07-04 16:04 被阅读12次

    源代码

    CoreFoundation源代码

    RunLoop的获取

    主线程的runloop

    CFRunLoopRef CFRunLoopGetMain(void) {
        CHECK_FOR_FORK();//判断是否需要fork 进程
        static CFRunLoopRef __main = NULL; // no retain needed
        if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
        return __main;
    }
    

    当前线程的runloop

    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        //先从TSD中查找有没有相关的runloop信息,有则返回。
        //我们可以理解为runloop不光存在与全局字典中,也存在中TSD中。
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    

    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) {
        //t为nil的时候,默认是使用主线程
        if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
        }
        //将线程和runloop之间关系通过一个全局字典保存起来
        //第一次进入的时候,会创建一个全局状态的,首先会为主线程创建一个runloop对象,并塞进该字典中,key是线程ID,value是runloop
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&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);
            __CFLock(&loopsLock);
        }
        //获取当前线程对应的runloop,如果没有就创建一个新的runloop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        //这里又使用了CFDictionaryGetValue方法获取loop,目的是为了防止
        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
            __CFUnlock(&loopsLock);
        CFRelease(newLoop);
        }
        //这边代码的作用:当线程销毁时,顺便也销毁其对应的runLoop
        //_CFSetTSD的代码位于CFPlatform.c文件中,传入PTHREAD_DESTRUCTOR_ITERATIONS-1的时候,会清除TSD中的储存着的runloop信息。(void (*)(void *))__CFFinalizeRunLoop 则会清空掉字典中,当前线程与runloop之间的关系
        //TSD: Thread-Specific Data 它是线程的一个数据存储单元,和全局变量很像,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的
        if (pthread_equal(t, pthread_self())) {//声明的线程ID和线程自身ID相等
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    

    通过源代码我们可以知道:

    1. runloop和线程之间是一一对应的,它们之间的关系保存在一个全局字典以及TSD中。
    2. 在线程创建的时候,是没有对应的runloop,runloop的创建是在第一次获取的时候,runloop的销毁则发生在线程销毁的时候。

    RunLoop的类和相关属性介绍

    在CFRunLoop中关于RunLoop的类一共有五个,它们分别是CFRunLoopRefCFRunLoopSourceRefCFRunLoopObserverRefCFRunLoopTimerRefCFRunLoopModeRef

    struct __CFRunLoop {
        CFMutableSetRef _commonModes;//common mode的集合
        CFMutableSetRef _commonModeItems;//每个common mode都有的item(source,timer and observer)集合
        CFRunLoopModeRef _currentMode;//当前runloop的mode
        CFMutableSetRef _modes;//所有的mode的集合
        ...
    };
    
    struct __CFRunLoopMode {
        CFStringRef _name;
        CFMutableSetRef _sources0;//source0的集合
        CFMutableSetRef _sources1;//source1的集合
        CFMutableArrayRef _observers;//observer的数组
        CFMutableArrayRef _timers;//timer的数组
        ...
       };
       
    //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;
    };
    
    typedef struct {
        CFIndex version;
        void *  info;
        const void *(*retain)(const void *info);
        void    (*release)(const void *info);
        CFStringRef (*copyDescription)(const void *info);
        Boolean (*equal)(const void *info1, const void *info2);
        CFHashCode  (*hash)(const void *info);
        //当source被添加到RunLoop中后,会调用这个回调
        void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
        //当调CFRunLoopSourceInvalidate 函数移除该source的时候,会执 此回调。
        void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
        //RunLoop处 该Source的时候会执 的回调
        void    (*perform)(void *info);
    } CFRunLoopSourceContext;
    
    typedef struct {
        CFIndex version;
        void *  info;
        const void *(*retain)(const void *info);
        void    (*release)(const void *info);
        CFStringRef (*copyDescription)(const void *info);
        Boolean (*equal)(const void *info1, const void *info2);
        CFHashCode  (*hash)(const void *info);
    #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
        //mach_port:通过内核对线程发送消息
        mach_port_t (*getPort)(void *info);
        void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
    #else
        void *  (*getPort)(void *info);
        void    (*perform)(void *info);
    #endif
    } CFRunLoopSourceContext1;
    

    总结5个类之间的关系:
    CFRunLoopRef对应的结构体中包含了若干Mode,而每个CFRunLoopModeRef中有包含了若干CFRunLoopSourceRefCFRunLoopObserverRefCFRunLoopTimerRef

    Current Mode

    runloop必须指定一个currentMode(_currentMode的赋值在CFRunLoopRunSpecific函数中,CFRunLoopRunSpecific函数在CFRunLoopRunCFRunLoopRunInMode中调用),如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入

    Source

    Source有两个版本:Source0和Source1,其中Source0只包含一个指针回调(perform),Source1除了这个指针回调以为还有一个mach_port。match_port是用于内核向线程发送消息的,所以Source1能够主动唤醒RunLoop线程,Source0需要先调CFRunLoopSourceSignal(source)将其标记为待处 ,然后再调用CFRunLoopWakeUp(runloop)来唤醒RunLoop以处理该事件。

    Common Mode

    在iOS下,主线程会预设两个Mode: kCFRunLoopDefaultMode 和 UITrackingRunLoopMode ,并且都被标记为“CommonModes”。

    什么是CommonModes?它其实是一个标识符,并不是一个具体的Mode。以addSource为例:

    void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return;
        if (!__CFIsValid(rls)) return;
        Boolean doVer0Callout = false;
        __CFRunLoopLock(rl);
        //当Mode为kCFRunLoopCommonModes
        if (modeName == kCFRunLoopCommonModes) {
        //如果_commonModes存在则获取一份拷贝数据
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        //_commonModeItems如果不存在则创建一个新的集合
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //将source添加到_commonModeItems
        CFSetAddValue(rl->_commonModeItems, rls);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rls};
            /* add new item to all common-modes */
            //调用__CFRunLoopAddItemToCommonModes函数向_commonModes中所有的Mode添加这个source
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
        } else {
            //此时该Mode不是CommonMode,从runloop中获取当前对应的Mode
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
            //当该Mode存在于RunLoop中,并且该Mode的_sources0集合为空
        if (NULL != rlm && NULL == rlm->_sources0) {
            //创建_sources0集合和_sources1集合
            rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
        }
            //当该Mode存在于RunLoop中,并且_sources0集合和_sources1集合中都不包含该Source
        if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
            //说明是Source0
            if (0 == rls->_context.version0.version) {
                CFSetAddValue(rlm->_sources0, rls);
            } else if (1 == rls->_context.version0.version) {//说明是Source1
                CFSetAddValue(rlm->_sources1, rls);
            __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
            if (CFPORT_NULL != src_port) {
                CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
                __CFPortSetInsert(src_port, rlm->_portSet);
                }
            }
            __CFRunLoopSourceLock(rls);
            if (NULL == rls->_runLoops) {
                rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
            }
            CFBagAddValue(rls->_runLoops, rl);
            __CFRunLoopSourceUnlock(rls);
            if (0 == rls->_context.version0.version) {
                if (NULL != rls->_context.version0.schedule) {
                    //如果是Source0,并且schedule回调 为空,标记为需要出发回调
                    doVer0Callout = true;
                }
            }
        }
            if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
        }
        __CFRunLoopUnlock(rl);
        if (doVer0Callout) {
            // although it looses some protection for the source, we have no choice but
            // to do this after unlocking the run loop and mode locks, to avoid deadlocks
            // where the source wants to take a lock which is already held in another
            // thread which is itself waiting for a run loop/mode lock
        rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
        }
    }
    

    一个Mode通过将其ModeName可添加到RunLoop的的“commonModes”中。每当RunLoop的内容发生变化时,RunLoop都会自动将_commonModeItems里的Source/Observer/Timer同步到具有“Common”标记的所有Mode里(这句话很重要)。

    这里我们做一下拓展,在面试的时候经常被问到的一个问题--为什么列表滑动的时候,NSTimer不执行回调?该如何解决?
    默认NSTimer是运行在RunLoop的kCFRunLoopDefaultMode下,在列表滑动的时候,RunLoop会进入UITrackingRunLoopMode,因为RunLoop只能运行在一种模式下,所以NSTimer不会执行回调。那如何解决呢?这似乎更简单,现成的API就有添加到CommonModes就可以了,这又是为什么呢?其实不难理解,kCFRunLoopDefaultModeUITrackingRunLoopMode都已经被标为”Common”属性的。这就解释了为什么添加到CommonModes的时候就可以解决NSTimer的回调问题。

    RunLoop运行

    RunLoop通过CFRunLoopRunCFRunLoopRunInMode这两个函数运行。

    void CFRunLoopRun(void) {   /* DOES CALLOUT */
        int32_t result;
        //线程就会一直停留在这个do-while的循环里直到result为kCFRunLoopRunStopped或者kCFRunLoopRunFinished
        //函数不会主动调用CFRunLoopStop函数(kCFRunLoopRunStopped)
    或者将所有事件源移除(kCFRunLoopRunFinished),从这里我们也可以了解,如果runloop的_currentMode值变化,只能退出,然后重新指定一个Mode进入
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    
    SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
        
    

    上述两个函数均调用了CFRunLoopRunSpecific这个函数,那接下来我们分析一下这个函数

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
        //是否释放
        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
        __CFRunLoopLock(rl);
        // 首先根据modeName找到对应Mode,如果没有则创建一个新的Mode
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
        //如果mode为空或者mode中没有相关的source/timer/observer
        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;
    
        //通知Observers:RunLoop即将进入loop
        if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        //真正的RunLoop运行方法:__CFRunLoopRun
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        //通知Observers: 退出runloop
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
            __CFRunLoopModeUnlock(currentMode);
            __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
        return result;
    }
    

    再看一下__CFRunLoopRun的代码(这里的代码很长,只贴关键性的代码)

    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm,
                                  CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
    {
        do { //通知观察者,即将处理Timer
            if (rlm->_observerMask & kCFRunLoopBeforeTimers)
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            //通知观察者,即将处理Source
            if (rlm->_observerMask & kCFRunLoopBeforeSources)
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            //处理加入的Block __CFRunLoopDoBlocks(rl, rlm);
            //处理Source0
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm,
                                                                  stopAfterHandle);
            if (sourceHandledThisLoop) {
                __CFRunLoopDoBlocks(rl, rlm);
            }
            //如果被MachPort唤醒,跳转到handle_msg
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer),
                                           &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
            //通知Observer,即将休眠
            if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting))
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            __CFRunLoopSetSleeping(rl);
            //调 mach_msg 等待接受 mach_port 的消息,线程将进 休眠,直到被唤醒 msg = (mach_msg_header_t *)msg_buffer;
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer),
                                       &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            //通知Observer,即将被唤醒
            if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
                __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            //处理消息 handle_msg:;
            //处理Timer,处理Source...
            //一系列判断,retVal是否为0, 不为0,继续loop
            
        } while (retVal == 0);
        }
    

    我们可以看到Runloop内部其实一直在一个循环里面,通过mach_msg()函数,在用户态和内核态中相互切换,RunLoop调用这个函数去接收消息,如果没有别人发送port消息过来,内核会将线程置于等待状态

    RunLoop的面试题分析

    以下代码的输出结果

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSInteger number = 1;
        NSLog(@"%zd", number);
        
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            [self performSelector:@selector(printString) withObject:nil afterDelay:0];
        });
        
        number = 3;
        NSLog(@"%zd", number);
    }
    
    - (void)printString {
        NSLog(@"sdasdas");
    }
    

    结果:1 3

    NSObject的performSelecter:afterDelay:performSelector:onThread:被调用时,实际添加了一个Timer到当前线程的RunLoop中,如果该线程没有RunLoop则不会执行这个方法。

    AutoReleasePool在什么么时候释放?

    App启动后,主线程的RunLoop被创建了两个Observer,分别在kCFRunLoopEntry进入的时候和kCFRunLoopBeforeWaiting即将休眠的时候执行回调。
    在进入时,会调用_objc_autoreleasePoolPush()创建自动释放池。
    在即将休眠时,会先调用_objc_autoreleasePoolPop(),接着再调用_objc_autoreleasePoolPush() ,释放旧池,创建新池。
    也就是说:通常在主线程的代码已经被RunLoop自动创建的AutoReleasePool包裹着,因此不会出现内存泄漏。当然,我们也可以创建自己的自动释放池干预某些对象的释放时机,以解决一些内存峰值的问题。

    两个线程如何实现交替打印

    思路:在A线程的RunLoop即将休眠kCFRunLoopBeforeWaiting的时候,主动去唤醒B线程的RunLoop,在B线程的RunLoop即将进入休眠的时候,主动唤醒A。代码大致如下:

    @interface ViewController ()
    
    @property (nonatomic, strong) NSThread *thread1;
    @property (nonatomic, strong) NSThread *thread2;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Run) object:nil];
        self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Run) object:nil];
       
        [self.thread1 start];
        [self.thread2 start];
    }
    
    - (void)thread1Run {
        //添加Observer用来唤醒其他线程的RunLoop
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            if (activity == kCFRunLoopBeforeWaiting) {
                NSLog(@"1");
                [self performSelector:@selector(weakUpThread) onThread:self.thread2 withObject:nil waitUntilDone:YES];
            }
        });
        
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
        CFRelease(observer);
        
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
    
    - (void)thread2Run {
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            if (activity == kCFRunLoopBeforeWaiting) {
                NSLog(@"2");
                [self performSelector:@selector(weakUpThread) onThread:self.thread1 withObject:nil waitUntilDone:NO];
            }
        });
        
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
        CFRelease(observer);
        
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
    
    - (void)weakUpThread {
        CFRunLoopWakeUp(CFRunLoopGetCurrent());
    }
    

    相关文章

      网友评论

        本文标题:RunLoop原理

        本文链接:https://www.haomeiwen.com/subject/bzgeuftx.html