美文网首页
RunLoop 的作用是什么?它的内部工作机制.

RunLoop 的作用是什么?它的内部工作机制.

作者: 吊炸 | 来源:发表于2019-11-08 13:06 被阅读0次

    概念

    RunLoop是一个相对抽象的概念,在程序运行中循环做一些事情,主要应用于:定时器(Timer)、PerformSelector、GCD Async To Main Queue、事件详情、手势识别、界面刷新、网络请求、AutoreleasePool
    我们想象一个场景:为什么App程序启动之后能够持续运行在前台呢?

    int main(int argc, char * argv[]){ 
    
    @autoreleasepool { 
    
     return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }   }
    
    UIApplicationMain的大致实现原理就是:(伪代码)
    
        int retVal = 0;
    
        do {
    
        int message = sleep_and_wait(); //睡眠中等待消息(比如响应点击各种事件)
    
        retVal = proess_message(message);//处理消息,更改返回值,如果为0,代表程序退出,不为0,程序持续运行。
    
        }while(retVal == 0);
    

    正因为RunLoop底层在执行一个while循环,来维持程序的不退出。

    RunLoop的基本作用:

    1. 保持程序不会马上退出,而是保持运行状态。
    2. 处理App中的各种事件(比如触摸,定时器时间等)
    3. 节省CPU资源,提高程序性能,有事做做事,没事做休眠。

    RunLoop跟线程的关系

    为什么聊Runloop一定要搭上线程,我们知道,程序里的每一句代码,都会在线程里面执行,下面要讲到获取Runloop对象的代码也不例外,一定是跑在线程里面的.之前我们说道,Runloop是为了让程序不退出,其实更准确地说,是为了保持某个线程不结束,只要还有未结束的线程,那么整个程序就不会退出,因为线程是程序的运行调度的基本单元.

    线程与Runloop的关系是一对一的,一个新创建的线程,是没有Runloop对象的,当我们在该线程里第一次通过上面的API获得Runloop时,Runloop对象才会被创建,并且通过一个全局字典将Runloop对象和该线程存储绑定在一起,形成一对一关系。

    Runloop会在线程结束时销毁,主线程的Runloop已经自动获取过(创建),子线程默认没有开启RunLoop(直到你在该线程获取它)。RunLoop对象创建后,会被保存在一个全局的Dictionary里,线程作为key,Runloop对象作为value。

    1. 每条线程都有唯一的一个与之对用的RunLoop对象
    2. RunLoop保存在一个全局的Dictionary中,线程作为key,RunLoop作为value
    3. 线程创建时并没有RunLoop对象,RunLoop会在第一次获取的时候创建。
    4. RunLoop会在线程结束的时候销毁
    5. 主线程的RunLoop已经自动创建,子线程默认不开启RunLoop。

    RunLoop有两种获取方式
    在OC中:[NSRunLoop currentRunLoop]
    C的:CFRunLoopRef runloop = CFRunLoopGetCurrent();
    启动一个runloop有以下三种方法:

    - (void)run;  
    
    - (void)runUntilDate:(NSDate *)limitDate;
    
    - (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
    

    这三种方式无论通过哪一种方式启动runloop,如果没有一个输入源或者timer附加于runloop上,runloop就会立刻退出。

    • 第一种方式,runloop会一直运行下去,在此期间会处理来自输入源的数据,并且会在NSDefaultRunLoopMode模式下重复调用runMode:beforeDate:方法;

    • 第二种方式,可以设置超时时间,在超时时间到达之前,runloop会一直运行,在此期间runloop会处理来自输入源的数据,并且也会在NSDefaultRunLoopMode模式下重复调用runMode:beforeDate:方法;

    • 第三种方式,runloop会运行一次,超时时间到达或者第一个input source被处理,则runloop就会退出。

    前两种启动方式会重复调用runMode:beforeDate:方法。

    我们还可以在CF源码里面详细看看,Runloop的信息是写在CF源码文件夹CFRunLoop.c文件里面,我们可以在里面搜索到CFRunLoopGetCurrent()函数的实现

    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    

    CFRunLoopGetCurrent()中又是通过_CFRunLoopGet0来获得Runloop对象的

    image

    Runloop对象底层结构

    我们可以在源码CFRunloop.c中找到Runloop的定义

    
    **************????__CFRunLoop????***********
    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
    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;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
     //♥️♥️♥️♥️♥️♥️♥️核心组成♥️♥️♥️♥️♥️♥️
    
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFAbsoluteTime _runTime;
        CFAbsoluteTime _sleepTime;
        CFTypeRef _counterpart;
    };
    
    
    **************????__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;
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
        //♥️♥️♥️♥️♥️♥️♥️核心组成♥️♥️♥️♥️♥️♥️
    
    
        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 */
    };
    

    退出RunLoop的方式

    第一种启动方式的退出方法

    文档说,如果想退出runloop,不应该使用第一种启动方式来启动runloop。
    如果runloop没有input sources或者附加的timer,runloop就会退出。
    虽然这样可以将runloop退出,但是苹果并不建议我们这么做,因为系统内部有可能会在当前线程的runloop中添加一些输入源,所以通过手动移除input source或者timer这种方式,并不能保证runloop一定会退出。

    第二种启动方式runUntilDate:

    可以通过设置超时时间来退出runloop。

    第三种启动方式runMode:beforeDate:

    通过这种方式启动,runloop会运行一次,当超时时间到达或者第一个输入源被处理,runloop就会退出。

    如果我们想控制runloop的退出时机,而不是在处理完一个输入源事件之后就退出,那么就要重复调用runMode:beforeDate:,

    具体可以参考苹果文档给出的方案,如下:

     NSRunLoop *myLoop  = [NSRunLoop currentRunLoop];
     myPort = (NSMachPort *)[NSMachPort port];
     [myLoop addPort:_port forMode:NSDefaultRunLoopMode];
    
    BOOL isLoopRunning = YES; // global
    
    while (isLoopRunning && [myLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    
    //关闭runloop的地方
    - (void)quitLoop
     {
        isLoopRunning = NO;
        CFRunLoopStop(CFRunLoopGetCurrent());
    }
    

    总之

    如果不想退出runloop可以使用第一种方式启动runloop;
    使用第二种方式启动runloop,可以通过设置超时时间来退出;
    使用第三种方式启动runloop,可以通过设置超时时间或者使用CFRunLoopStop方法来退出。

    相关文章

      网友评论

          本文标题:RunLoop 的作用是什么?它的内部工作机制.

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