RunLoop详解

一、CFRunLoop部分源码

阅读源码:CFRunLoop.c

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
  ...
  //从字典获取runloop
  CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  __CFUnlock(&loopsLock);
  // 如果没获取到
  if (!loop) {
     //创建新的runloop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
    __CFLock(&loopsLock);
      // 从字典里再找
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
          //如果还没有 就把新的存进去
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
          // 新创建的赋值给loop
        loop = newLoop;
    }
          // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    __CFUnlock(&loopsLock);
    CFRelease(newLoop);
  }
  ...
}

从源码中可以看出

一个线程对应一个RunLoop

RunLoop保存在一个全局的字典里 线程为key RunLoop作为Value

线程刚创建的时候没有Ru nLoop对象,RunLoop会在第一次获取它的时候创建

主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop 只有调用[NSRunLoop currentRunLoop] 才创建runLoop

由于key是线程 所以在线程结束的时候RunLoop也会随之销毁

二 Runtime相关的类

1、Core Fuoundation中愿意RunLoop有5个类

CFRunLoopRef
CFRunLoopModelRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

2、CFRunLoop

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
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

重点看下面几个

struct __CFRunLoop {
   // 线程对象
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode; //当前模式CFRunLoopMode
    CFMutableSetRef _modes; // CFRunLoopModes
}

看一下CFRunLoopModeRef

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
 ...
    char _padding[3];
    CFMutableSetRef _sources0;  //CFRunLoopSource
    CFMutableSetRef _sources1;  //CFRunLoopSource
    CFMutableArrayRef _observers;  //CFRunLoopObserver
    CFMutableArrayRef _timers; //CFRunLoopTimer
...
};

3、RunLoop结构

Sources0

触摸事件处理

performSelector:OnThread:

Source1

基于Port的线程间通信

系统事件的捕捉(source1 捕捉 source0处理)

Timers

NSTimer

performSelector:withObject:afterDealy:

observer

用于监听RunLoop的状态

UI刷新(BeforeWaiting)

AutoRelease pool

observer 监听类型
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 进入runloop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将进入timers
    kCFRunLoopBeforeSources = (1UL << 2), // 即将进入source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), //刚从个休眠中唤醒
    kCFRunLoopExit = (1UL << 7), //即将推出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • 如果model里没有任何source0/source1/observers/timers 会退出runloop
  • CFRunLoopMode
    1. CFRunLoopDefaultMode 默认
    2. UITrackingRunLoopMode 用于界面跟踪Mode用于scrollview追踪触摸滑动,保证界面滑动不受其他的mode影响
    3. CFRunLoopCommonModes

4、RunLoop运行逻辑

通过在打印调用栈 找到CFRunLoopRunSpecific

我们查看源码 看一下RunLoop的运行逻辑

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
   
    //如果有timer 处理timer   ->kCFRunLoopBeforeTimers
    if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    //如果有Source 处理Source - >kCFRunLoopBeforeSources
    if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
   // 处理Block
__CFRunLoopDoBlocks(rl, rlm);
    //处理Source0  如果Source有block 处理Source0的block ->__CFRunLoopDoSources0
    Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    if (sourceHandledThisLoop) {
        __CFRunLoopDoBlocks(rl, rlm);
}

    if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
        msg = (mach_msg_header_t *)msg_buffer;
        // 处理Source1中的事件 port  __CFRunLoopServiceMachPort 如果有事件  goto-> handle_msg
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }

    }
    //即将休眠 __CFRunLoopSetSleeping
	if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	__CFRunLoopSetSleeping(rl);

        do {
           
            //等待唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);

        // 被唤醒后 调用  即将唤醒
	__CFRunLoopUnsetSleeping(rl);
        //继续监听
	if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    
        handle_msg:;
    
       if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // 如果有timer 处理timer; ->__CFRunLoopDoTimers
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }  else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // 如果有timer 处理timer; ->__CFRunLoopDoTimers

            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        // GCD唤醒
        else if (livePort == dispatchPort) {
            // ansy to MainQueue  同步到主线程
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            
        } else {
            //处理Source1
		sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;

            
            // Restore the previous voucher
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
        }

    //处理block
	__CFRunLoopDoBlocks(rl, rlm);
        
        //设置返回值
	if (sourceHandledThisLoop && stopAfterHandle) {
	    retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
	} else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
	    retVal = kCFRunLoopRunStopped;
	} else if (rlm->_stopped) {
	    rlm->_stopped = false;
	    retVal = kCFRunLoopRunStopped;
	} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
	    retVal = kCFRunLoopRunFinished;
	}
    
    return retVal;
}

mach_msg()直接睡眠 内核层面

  • [ NSRunLoop currentRunLoop]run ] 是无法停止的 是一个永不销毁的线程。//可以看方法注释
  • CFRunloopRunInMode 第三个参数是 执行完source后是否退出
  • 结构体初始化最好赋值为{0} 避免内存垃圾

本文分享自微信公众号 - 老沙课堂(gh_f73a6b772d4f),作者:rui4u

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • isa详解(一)isa结构

    为什么要用union以及位运算呢。因为在计算机中为二进制。位运算是最快速的计算方式 union C++ 中的共用体。顾名思义 就是在union 中 公用一个内存...

    老沙
  • 据结构与算法(十) AVL树

    因为无法改变添加删除顺序(用户操作决定),所以在每次操作之后,让二叉树达到平衡状态。

    老沙
  • 浅入深出Copy和mutableCopy

    由Tagged Pointed 可以知道a b 为Tagged Pointer 对象 想深入了解的的可以看一下我的上一篇文章

    老沙
  • 老司机出品——源码解析之RunLoop详解

    不得不说,人的惰性是真可怕啊。 从上周六就到写runLoop的建议开始,星期三告诉自己从星期四开始着手写这篇博客。然而现在戳个时间戳,现在是4.30星期日。写...

    老司机Wicky
  • java实现发送邮件服务器,SMTP协议发送邮件

    1.采用SMTP的邮件发送协议。准备:在网易注册一个邮箱,进入设置开启SMTP/pop3协议 ? 2.接下来就是java代码实现了,下面都有注释,就不多做解释了...

    生活创客
  • 你看到的地图是这个世界的真实面目吗?

    在数据可视化中,除了常见的折线图条形图,还有一种很常见的图示,那就是地图。今天呢,我们就说说地图中的那些门道。

    卤代烃
  • matlab版的origin呼之欲出,快来领取吧!

    没有设置坐标轴、没有调整刻度线、没有设置字体等等,matlab默认是啥画的就是啥。有的小伙伴很聪明,干脆就用matlab搞计算、用origin画图,这样就可以完...

    matlab爱好者
  • 即使删了全库,保证半小时恢复

    近期一篇《就这样把根目录删了!!!》引发了广泛的讨论,《如何防止根目录被删》汇总了7种防删方案。还有同学评论中反馈“不小心把库删了”,如何快速恢复删掉的数据库,...

    架构师之路
  • 国内某大型支付系统MySQL架构

    整体来讲,这个结构是符合金融级架构,如果你也在做金融级的MySQL方案,或是涉及到多DC的处理,也可以参考一下。

    wubx
  • 小技巧 | Burpsuite爆破含CSRF-Token的程序

    1. 抓包 0x01 开启burpsuite代理,抓取数据包,将请求包转送到Intruder

    HACK学习

扫码关注云+社区

领取腾讯云代金券