RunLoop源码阅读RunLoop源码阅读

2021-08-09 14:23:11 浏览数 (1)

[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。

0 人点赞