RunLoop源码详解

RunLoop是什么

如果没有RunLoop,那么该线程在执行完自己的任务时就会退出;而我们在使用APP时,通常不会只执行完一个任务就需要其退出,那么我们就需要一种机制,在我们没有任务时依然可以保证线程不退出,随时可以处理事件,这就需要用到RunLoop了。
RunLoop,顾名思义:运行循环,在其内部就是一个大的do-while循环,在这个循环里执行了各种操作,保证线程不退出,程序持续运行。其存在的目的就是:在保证线程不退出的前提下,当线程有任务时执行任务,在线程没有任务的时候就让其睡眠,节省CPU资源,提高程序性能。

NSRunLoop和CFRunLoop

苹果官方源码
我们平时所说的RunLoop有两种,一种是NSRunLoop,一种是CFRunLoop
将下列代码写在主线程中,即主RunLoop和当前的RunLoop是同一个

NSLog(@"%p %p", [NSRunLoop mainRunLoop], [NSRunLoop currentRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());

在这里插入图片描述
打印结果显示NSRunLoop和CFRunLoop地址不一样,再打印NSRunLoop的信息看看

NSLog(@"%@", [NSRunLoop mainRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());

在这里插入图片描述
根据箭头所指可以得知,NSRunLoop只是比CFRunLoop多了一层简单的OC封装,底层还是CFRunLoop,CFRunLoop本质是一个结构体,而NSRunLoop是一个NSObject对象。且NSRunLoop存在于Foundation框架中,CFRunLoop是存在于CoreFoundation框架中的。NSRunLoop不是线程安全的,CFRunLoop时候线程安全的。那我们现在看看CFRunLoop是如何实现的

RunLoop内部结构

在分析源码之前,我们先大致了解一下RunLoop的内部结构。CoreFoundation里面关于RunLoop有五个类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

他们之间的关系为:(图来源于深入理解RunLoop
在这里插入图片描述
即一个RunLoop里有许多个Mode,一个Mode里有一个Source的set,一个Observer的array和一个Timer的array。这里要注意,虽然一个RunLoop里存在许多Mode,但是每次只能使用一个Mode,需要更换时就退出当前Mode,换下一个Mode

RunLoop基本结构体源码

__CFRunLoop结构体

我们先看看__CFRunLoop结构体是怎么组成的

struct __CFRunLoop {
   
   
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */  // 访问mode集合时用到的锁
    __CFPort _wakeUpPort;			// used for CFRunLoopWakeUp   // 手动唤醒runloop的端口。初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
    Boolean _unused;
    volatile _per_run_data *_perRunData;           // reset for runs of the run loop  初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
    pthread_t _pthread;                            // 与该RunLoop关联的线程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;                  // 存储的是字符串,记录所有标记为common的mode
    CFMutableSetRef _commonModeItems;              // 存储所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;                 // 当前运行的mode
    CFMutableSetRef _modes;                        // 该RunLoop中所有的mode
    struct _block_item *_blocks_head;              // 链表头指针,该链表保存了所有需要被runloop执行的block。外部通过调用CFRunLoopPerformBlock函数来向链表中添加一个block节点。runloop会在CFRunLoopDoBlock时遍历该链表,逐一执行block
    struct _block_item *_blocks_tail;              // 链表尾指针,之所以有尾指针,是为了降低增加block时的时间复杂度
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

从__CFRunLoop结构体可以得知:

  1. RunLoop和线程是一一对应的,一个RunLoop包含一个线程,当该线程结束时RunLoop自动销毁
  2. 一个RunLoop只有一个_currentMode,即当前运行的mode
  3. _commonModes 表示NSRunLoopCommonModes这个模式下保存的Mode,我们也可以将自定义的Mode添加到这个set里面
  4. _commonModeItem 表示添加到NSRunLoopCommonModes里面的Source/Timer/Observer等
  5. _commonModes里面保存的是被标记为common的mode。这种标记为common的mode有种特性,那就是当 RunLoop 的内容发生变化时,RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。可以这样理解,RunLoop中的_commonModeItems由被标记为common的mode下的各个item(source、observe、timer)组成

__CFRunLoopMode结构体

上文谈到了许多次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];
    // 几种事件,下面这四个字段,在苹果官方文档里面称为Item
    // RunLoop中有个commomitems字段,里面就是保存的下面这些内容
    CFMutableSetRef _sources0;
    CFMutableSetRef _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 */
};

需要注意:

  1. 一个CFRunLoopMode对象中有一个name,许多source0,许多source1,许多Timer,许多Observer和许多port
  2. 我们在下文会提到的kCFRunLoopDefaultMode就是mode的name,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef
  3. 对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除
  4. source、timer、observer可以在多个mode中注册,但是只有RunLoop当前的currentMode下的source、timer、observer才可以运行

RunLoop中的mode主要分为以下几种:

  1. KCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动的时候不受其他Mode影响
  3. KCFRunLoopCommonMode:这是一个占位用的Mode,作为标记KCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode(也可以理解为是上面两种mode的结合)
  4. UIInitializationRunLoopMode:在刚启动App的时候进入的第一个Mode,启动完成后不再使用
  5. CSEventReceiveRunLoopMode:接受系统事件内部Mode,通常用不到

综合以上,我们可以得知,RunLoop里只保存了许多mode,而mode里保存的才是实际执行的任务
接下来我们剖析mode里包含的三种item

__CFRunLoopSource结构体

struct __CFRunLoopSource {
   
   
    CFRuntimeBase _base;
    uint32_t _bits;    //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
   
   
        // version0、version1 是根据对不同事件的处理区分出来的source0、source1
		CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};

可以看出,Source根据要处理的事件不同分为两个版本,version0和version1,我们分别探究两个的结构体:

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);
    void	(*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);  //当source加入到mode触发的回调
    void	(*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);    //当source从RunLoop中移除时触发的回调
    void	(*perform)(void *info);  //当source事件被触发时的回调,使用CFRunLoopSourceSignal方式触发
} CFRunLoopSourceContext;

即:
source0 只包含了回调(函数指针),source0是需要手动触发的Source,它并不能主动触发事件,必须要先把它标记为signal状态。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,也就是通过uint32_t _bits来实现的,只有_bits标记Signaled状态才会被处理。然后手动

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值