前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RunLoop源码阅读RunLoop源码阅读

RunLoop源码阅读RunLoop源码阅读

作者头像
用户8893176
发布2021-08-09 14:23:11
1.1K0
发布2021-08-09 14:23:11
举报
文章被收录于专栏:小黑娃Henry小黑娃Henry

[toc]

RunLoop概念

u=2103244534,416163775&fm=26&gp=0.gif

代码语言:javascript
复制
func loop() {
    repeat {
        var signal = getNextMessage()
        process_message(signal)
    }while signal != quit
}
  1. RunLoop是iOS、OSX开始中非常基础的概念,也是操作系统中非常重要的一环;
  2. 实际上是一个对象,该对象负责事件、消息的接受和处理;
  3. 关键点在于:无事件避免资源占用,有事件立即响应。

mach_msg(mach消息转发机制)

系统内核在收发事件、消息时使用的消息传递函数。可以理解为多进程之间的一种通讯调用机制。

代码语言:javascript
复制
extern mach_msg_return_t        mach_msg(
    mach_msg_header_t *msg, //消息头
    mach_msg_option_t option,      //消息方式(发送、接受)
    mach_msg_size_t send_size, 
    mach_msg_size_t rcv_size,   //缓冲区域大小,做计算使用
    mach_port_name_t rcv_name,
    mach_msg_timeout_t timeout, //该消息的超时时间,超过这个事件runloop就会进入下次loop或者sleep
    mach_port_name_t notify);   //消息完成后的其他通讯名称
 
 typedef struct{
    mach_msg_bits_t       msgh_bits;    //标示位
    mach_msg_size_t       msgh_size;    //大小
    mach_port_t           msgh_remote_port; //目标端口
    mach_port_t           msgh_local_port;  //源端口
    mach_port_name_t      msgh_voucher_port;
    mach_msg_id_t         msgh_id;
} mach_msg_header_t;

unsigned int 无符号的Int,可以修饰char、int signed int 有符号的Int,我们常用的Int就是这个类型

线程和RunLoop的关系

代码语言:javascript
复制
CFRunLoopGetMain(void) //获取主线程的runloop
CFRunLoopGetCurrent(void) //获取当前线程的runloop

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
//如果是当前线程是空的,则获取主线程
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
//线程锁,防止多个线程读取全局静态字典__CFRunLoops
    __CFLock(&loopsLock);
//如果全局字典为空,在线程创建后就是空值,也就是说线程创建后默认没有runloop
    if (!__CFRunLoops) {
    //创建一个新的临时字典
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    //获取主线程的Runloop,并存到字典中
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    //将临时字典和全局字典做了一次copy操作,并释放临时字典。由于copy操作是临时字典的引用计数+1
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }CFRelease(mainLoop);}
    //通过全局字典获取当前线程的runloop
    CFRunLoopRef newLoop = NULL;
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //若不存在则创建,并写入全局字典中
    if (!loop) {
    newLoop = __CFRunLoopCreate(t);
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    __CFUnlock(&loopsLock);
    //2次赋值增加了对应的引用计数
    if (newLoop) { CFRelease(newLoop); }
    
    if (pthread_equal(t, pthread_self())) {
     //把runloop写入当前线程的私有数据中
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
        //在线程私有对象中注册一个回调,当线程销毁时将runloop也销毁了
        //__CFFinalizeRunLoop是runloop的析构函数
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

RunLoop原理

一种在当前线程,持续调度各种任务的运行机制

u=2103244534,416163775&fm=26&gp=0.gif

RunLoop原型

代码语言:javascript
复制
while (alive) {
  performTask()
  callout_to_observer()
  sleep()
}

事件处理

每次运行都会执行若干个task,执行task的方式有很多:

  1. DoBlocks() 这种方式可以被开发者使用
  2. DoSources0() 可对外使用
  3. DoSources1() 只能供系统使用
  4. DoTimers() NSTimer相关
  5. DoMainQueue() 开发者调用 GCD 的 API 将任务放入到 main queue 中

对外通讯

代码语言:javascript
复制
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity)

对外通知统一使用此方法,根据参数可以看到只有:kCFRunLoopBeforeTimers,kCFRunLoopBeforeSources几个方式,其他的应该是无法准确控制任务执行时长。

睡眠

代码语言:javascript
复制
__CFRunLoopServiceMachPort(::::)

睡眠后有4种情况可以唤醒runloop:

  1. 基于port的source事件
  2. timer事件
  3. runloop超时
  4. 外部手动触发唤醒

事实上runloop的执行时很复杂的,会交叉进行,并不是看到的这样简单.

runloop在一次loop中可能会做的事

代码语言:javascript
复制
while (alive) {
  //执行任务
  DoBlocks();
  DoMainQueue();
  DoObservers-Sources();
  DoSources0();
  DoSources1();
  DoObservers-Timer();
  DoTimers();
 
  
  //通知外部
  DoObservers-Waiting();
    
  //休眠
  sleep() 
}

//可以通过代码验证一下:
let obs = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0) { (observer, activity) in
            print("Status has changed into: \(activity)")
        }
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), obs, CFRunLoopMode.defaultMode)

Source/Timer/Observer 被称为modeItem

mode是RunLoop的运行模式,modeItem是模式中的因子(事件本身)

代码语言:javascript
复制
struct __CFRunLoop {
    __CFPort _wakeUpPort;//内核向该端口发送消息可以唤醒runloop
    CFMutableSetRef _commonModes;//Set存储的是字符串,记录所有标记为common的mode,标识符
    CFMutableSetRef _commonModeItems; //Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;//当前运行的mode
    CFMutableSetRef _modes;// Set
    ...
};

而一次loop对应的对象就是mode,把loop的相关信息都定义成一个结构体

代码语言:javascript
复制
struct __CFRunLoopMode {
    ...
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    ...
};
  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行;nstimer
  • UITrackingRunLoopMode:界面跟踪Mode,用于滚动视图追踪触摸滑动,保证界面滑动时不受其他 Mode影响;
  • UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode;
  • GSEventReceiveRunLoopMode:接受系统事件的内部Mode;
  • kCFRunLoopCommonModes:这是一个占位用的Mode,并不是一种真正的Mode;

commonModes

CommonModes是一个标识符,并不是一个具体的Mode。每当RunLoop的内容发生变化时,RunLoop都会自动将commonModeItems里的Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。

RunLoop运行

代码语言:javascript
复制
void CFRunLoopRun(void) {
    int32_t result;
    do {
    //默认在default的modl
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

//runloop的核心方法

代码语言:javascript
复制
//CFRunLoopRef 当前runloop
//CFRunLoopModeRef 当前mode
//seconds 超时唤醒时间
//stopAfterHandle 处理后是否停止
//previousMode 上一次loop的modl
static int32_t __CFRunLoopRun(::::){
//runloop、mode停止,返回停止状态
if (__CFRunLoopIsStopped(rl)) {
    __CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//主端口
__CFPort dispatchPort = CFPORT_NULL;
...
//mode端口
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
...
//唤醒runloop
//在当前线程下创建计时器
//在没有任何msg消息的情况下根据超时时间,超时后唤醒
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置时间上下线文
dispatch_set_context(timeout_timer, timeout_context);
//时间函数的回调,用来激活runloop
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
//时间函数的取消回调
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
//只执行一次

//获取当前mode的端口
__CFPortSet waitSet = rlm->_portSet;
//通知Observers,runloop将触发 Before Timer && Source
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//执行加入的block
__CFRunLoopDoBlocks(rl, rlm);
//执行Sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//执行由Sources0分配来的block
    __CFRunLoopDoBlocks(rl, rlm);
}
//如果有Source1(基于port)处于ready状态,直接处理这个Source1然后跳转去处理消息。
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
//获取到内核的消息
    msg = (mach_msg_header_t *)msg_buffer;
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {
        goto handle_msg;
    }}
//通知Observers,runloop即将进入sleep
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//runloop进入睡眠
    __CFRunLoopSetSleeping(rl);
do{
//通过调用mach_msg,等待接受machPort的消息,但并不处理。线程将进入休眠,直到被下面某一个事件唤醒,或者该runloop被销毁
//__CFRunLoopServiceMachPort相同方法参数不同,分别表示查询到立刻返回和一直等待有消息再返回;将对应port赋值给liveport
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
}while{}
//唤醒且开始接受事件
__CFRunLoopSetIgnoreWakeUps(rl);
__CFRunLoopUnsetSleeping(rl);
//接收到某个时间被唤醒runloop被唤醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//处理mach_msg消息成功的标签
handle_msg:;
//如果一个Timer到时间了,触发这个Timer的回调, 且重新布置下一次计时器
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
    CFRUNLOOP_WAKEUP_FOR_TIMER();
    if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // Re-arm the next timer
        __CFArmNextTimerInMode(rlm, rl);
    }
}
//主端口
else if (livePort == dispatchPort) {
    //通过线程的私有方法里添加gcdMain
    _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
    //如果有dispatch到main_queue的block,执行block。
    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
//剩余port表示只剩source1,则获取对应source1,或没获取到则抛出异常
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
//执行对应source1
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
//创建且销毁source1的对应mach_msg
if (NULL != reply) {
    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
//执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
//根据参数处理完事件返回
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
//超出入参的超时时间
} else if (timeout_context->termTSR < mach_absolute_time()) {
    retVal = kCFRunLoopRunTimedOut;
//runloop被停止
} else if (__CFRunLoopIsStopped(rl)) {
    __CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
//mode被停止
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
//runloop中没有mode可执行
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
do『...} while (retVal == 0);
//释放timer

应用:

  1. 实现线程保活(AFNetworking)
  2. AutoreleasePool
  3. 事件响应
  4. 手势识别
  5. GCD
  6. NSTimer
  7. PerformSelecter
  8. 界面刷新

参考:

https://blog.ibireme.com/2015/05/18/runloop/ https://opensource.apple.com/tarballs/ https://github.com/apple/swift-corelibs-foundation/(Swift 开源后,苹果又维护了一个跨平台的 CoreFoundation 版本)

后知后觉的几个点: 1.gcd的定时器并不是基于runtime,它是高于runtime,runtime是基于gcd定时器 2.Commonmode是defaultmode和trackMode的集合!一般情况下被commone就是同时放入这两个mode的itemmode中 3,runloop是存在__CFRunLoops,而__CFRunLoops是一个全局的字典,和runloop本身无关。以线程名为KEY。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019/11/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • RunLoop概念
  • mach_msg(mach消息转发机制)
  • 线程和RunLoop的关系
  • RunLoop原理
    • 事件处理
      • 对外通讯
        • 睡眠
        • mode是RunLoop的运行模式,modeItem是模式中的因子(事件本身)
          • commonModes
          • RunLoop运行
          • 应用:
          • 参考:
          相关产品与服务
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档