姬長信(Redy) 大佬早!

源码

RunLoop学习笔记

在一般情况下,一个线程在执行完了一个任务后就会自动退出。我们想要有这样一个机制,让线程随时可以处理事件但是不退出。RunLoop实际就是一个对象,这个对象提供了一个入口函数,线程执行了这个函数后,会一直处在这个函数循环中,接收消息->等待->处理,直到循环结束。

RunLoop概念

RunLoop介绍

[图片上传失败...(image-663bc5-1547089087030)]
从代码上看,RunLoop就是一个对象,它的结构如下,源码在这里

struct __CFRunLoop {
    CFRuntimeBase _base;    pthread_mutex_t _lock;  /* locked for accessing mode list */
    __CFPort _wakeUpPort;   // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloop
    Boolean _unused;    volatile _per_run_data *_perRunData; // reset for runs of the run loop
    pthread_t _pthread;             //RunLoop对应的线程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;    //存储的是字符串,记录所有标记为common的mode
    CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;   //当前运行的mode
    CFMutableSetRef _modes;          //存储的是CFRunLoopModeRef
    struct _block_item *_blocks_head;//doblocks的时候用到
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

可以看到,一个RunLoop对象,主要包含一个线程,若干mode,若干commoMode,若干commonModeItem,还有一个当前运行的mode。

RunLoop和线程

RunLoop就是管理线程的,当线程的RunLoop开启后,线程就会在执行任务后,处于休眠状态,随时等待接收新的任务被唤醒,而不是退出。

只有主线程的RunLoop是默认开启的,所以程序启动后,主线程会一直运行,不会退出。其他线程默认是没有RunLoop的,所以在执行完了任务后线程会自动退出,但是当我们去获取当前线程的RunLoop时,系统会自动创建该线程对应的RunLoop。

RunLoop Mode

Mode可以看做是事件的管家,一个Mode管理着各种事件,它的结构如下:

struct __CFRunLoopMode {
    CFRuntimeBase _base;    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;   //mode名称
    Boolean _stopped;    //mode是否被终止
    char _padding[3];    //几种事件
    CFMutableSetRef _sources0;  //sources0
    CFMutableSetRef _sources1;  //sources1
    CFMutableArrayRef _observers; //通知
    CFMutableArrayRef _timers;    //定时器
    CFMutableDictionaryRef _portToV1SourceMap; //字典  key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    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 */};

一个CFRunLoopModeRef对象有一个name,若干source0,source1,timer,observer和port,可以看出事件都是由mode在管理,而RunLoop管理着Mode。
从源码可以看出,RunLoop总是运行在某个特定的Mode下,每个RunLoop都可以包含若干个Mode,每个Mode又包含source,timer,observer。每次调用RunLoop的主函数_CFRunLoopRun()时必须指定一种Mode。这个Mode称为currentMode,当切换Mode时必须退出当前Mode。然后重新进入RunLoop以保证不同的Mode的source,timer,observer互不影响。
[图片上传失败...(image-3cd7a0-1547089087030)]
如上图所示,RunLoop实际就是source,timer,observer的集合,不同的mode把不同组的source,timer,observer隔绝开来。RunLoop在某个时刻只能泡在一个Mode下,处理这一个mode中的source,timer,observer。
苹果文档中提到的Mode有五个:

  • NSDefaultRunLoopMode

  • NSConnectionReplyMode

  • NSModalPanelRunLoopMode

  • NSEventTrackingRunLoopMode

  • NSRunLoopCommonModes
    iOS中暴露的Mode只有NSDefaultRunLoopModeNSRunLoopCommonModes,NSRunLoopCommonModes是一个Mode的集合,默认包括NSDefaultRunLoopModeNSEventTrackingRunLoopMode,当我们把事件源加入到NSRunLoopCommonModes这种Mode时,就等于是把事件源同时加入了NSDefaultRunLoopModeNSEventTrackingRunLoopMode这两种Mode。我们也可以通过CFRunLoopAddCommonMode()这个方法将自定义的Mode加入到kCFRunLoopCommonModes组合中。

RunLoop Source

RunLoop Source分为Source,Observer,Timer三种,他们统称为ModeItem。

CFRunLoopSource

CFRunLoopSource分为source0和source1,它的结构如下:

struct __CFRunLoopSource {    CFRuntimeBase _base;
    uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
    pthread_mutex_t _lock;    CFIndex _order;         /* immutable */
    CFMutableBagRef _runLoops;    union {        CFRunLoopSourceContext version0;     //version0和version1决定是source0还是source1
        CFRunLoopSourceContext1 version1;    
    } _context;
};

source0是APP内部事件,由APP自己管理的UIEvent都是source0。当一个source0事件准备执行的时候,必须先把他标记为signal状态,以下是source0的结构体:

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);    void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);    void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);    void    (*perform)(void *info);
} CFRunLoopSourceContext;

source0是飞非基于port的,它包含了一个回调,并不能主动触发事件。使用时需要先调用CFRunLoopSourceSignal(source),将这个source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。

source1由内核和RunLoop管理。source1带有mach_port_t,可以接收内核消息并触发回调,以下是source1的结构体:

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_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;
CFRunLoopObserver

CFRunLoopObserver是RunLoop状态的监听者,可以监听RunLoop的各种状态并执行回调:

struct __CFRunLoopObserver {    CFRuntimeBase _base;
    pthread_mutex_t _lock;    CFRunLoopRef _runLoop;      //哪个RunLoop对象
    CFIndex _rlCount;    CFOptionFlags _activities;      //监听的是哪种状态
    CFIndex _order;         /* immutable */
    CFRunLoopObserverCallBack _callout; //回调
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */};

CFRunLoopObserver可以观察的状态有以下6种:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即将进入run loop
    kCFRunLoopBeforeTimers = (1UL << 1), //即将处理timer
    kCFRunLoopBeforeSources = (1UL << 2),//即将处理source
    kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),//被唤醒但是还没开始处理事件
    kCFRunLoopExit = (1UL << 7),//run loop已经退出
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
CFRunLoopTimer

CFRunLoopTimerNSTimertoll-free-bridged,可以在设定的时间点抛出回调:

struct __CFRunLoopTimer {    CFRuntimeBase _base;
    uint16_t _bits;  //标记fire状态
    pthread_mutex_t _lock;    CFRunLoopRef _runLoop;        //添加该timer的runloop
    CFMutableSetRef _rlModes;     //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
    CFAbsoluteTime _nextFireDate;    CFTimeInterval _interval;     //理想时间间隔  /* immutable */
    CFTimeInterval _tolerance;    //时间偏差      /* mutable */
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context; /* immutable, except invalidation */};

RunLoop的实现

下面从以下3个方面来介绍RunLoop的实现:

  • 获取RunLoop

  • 添加Mode

  • 添加RunLoop Source

获取RunLoop

Apple不允许我们直接创建RunLoop对象,只能通过以下几个函数来获取:

  • CFRunLoopRef CFRunLoopGetCurrent(void)

  • CFRunLoopRef CFRunLoopGetMain(void)

  • +(NSRunLoop *)currentRunLoop

  • +(NSRunLoop *)mainRunLoop
    前两个是Core Foundation的API,后两个是Foundation中的API。
    那么RunLoop什么时候创建呢?我们进入函数内部来看:

CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);    if (rl) return rl;   //传入当前线程作为参数
    return _CFRunLoopGet0(pthread_self());
}

CFRunLoopGetCurrent(void)函数内部调用了_CFRunLoopGet0(),传入的参数是当前线程。

CFRunLoopGetMain
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();    static CFRunLoopRef __main = NULL; // no retain needed//传入主线程作为参数
    if (!__main) __main = _CFRunLoopGet0(_CFMainPThread); // no CAS needed
    return __main;
}

CFRunLoopGetMain(void)最终也是调用了CFRunLoopGet0,传入了主线程作为参数,所以这个方法不管是在主线程还是子线程中都能获得主线程的RunLoop。

CFRunLoopGet0

前面两个方法最终都是调用了CFRunLoopGet0这个方法,下面我们看一下这个方法的具体实现:

struct __CFRunLoopMode {
    CFRuntimeBase _base;    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;   //mode名称
    Boolean _stopped;    //mode是否被终止
    char _padding[3];    //几种事件
    CFMutableSetRef _sources0;  //sources0
    CFMutableSetRef _sources1;  //sources1
    CFMutableArrayRef _observers; //通知
    CFMutableArrayRef _timers;    //定时器
    CFMutableDictionaryRef _portToV1SourceMap; //字典  key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    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 */};

0

这段代码可以得出下列结论:

  • RunLoop和线程一一对应,对应的方式是以key-value的方式保存在一个全局字典中。

  • 主线程的RunLoop会在初始化全局字典时创建。

  • 子线程的RunLoop会在第一次获取的时候创建,如果不获取的话就一直不会被创建。

  • RunLoop会在线程销毁的时候销毁。

RunLoop运行

(0)

本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/runloopxuexibiji.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:1 月 21, 2019 at 06:04 下午

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复