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

2019-11-08  本文已影响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就会立刻退出。

前两种启动方式会重复调用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方法来退出。

上一篇下一篇

猜你喜欢

热点阅读