美文网首页程序员
RunLoop源码分析

RunLoop源码分析

作者: 箫声乱 | 来源:发表于2018-06-02 15:48 被阅读0次

    之前一直对ios的RunLoop机制一知半解,很多地方不是很清楚于是每次想到这个问题都会纠结,想搞明白这里边到底做了一些什么事情。最近一周时间稍微宽裕,终于抽出来一些时间去阅读了一下CoreFoundation中的关于RunLoop的源码。可以在这里下载得到。这里有很多版本,我下载了CF-1151.16版本。

    一、什么是RunLoop

    关于RunLoop简单的理解就是一层do-while循环,在这层循环中不断的去监听处理一些事件源,比如定时器事件、触摸事件等,当没有任何事件可处理时,它就会去睡眠。当然,当条件不满足时比如超时或者主动退出等其他因素退出时会跳出这层循环。

    CFRunLoop基于pthread来管理,主线程的runloop自动创建,子线程的runloop默认不创建。runLoop不通过alloc init来创建,要获得runLoop可以通过函数CFRunLoopGetMain( )和CFRunLoopGetCurrent( )来获得主线程、当前线程的runloop(实质是一种懒加载)。

    二、RunLoop相关数据结构

     CFRunLoopRef:一个指向__CFRunLoop的指针
         struct __CFRunLoop {
        ...
        pthread_mutex_t _lock;           //线程锁对于Modelist的同步访问
        __CFPort _wakeUpPort;           //RunLoop的唤醒端口
        Boolean _unused;
        pthread_t _pthread;             //runLoop所处的线程
        CFMutableSetRef _commonModes;    //存储了属于CommonModes的CFRunLoopmode
        CFMutableSetRef _commonModeItems; //属于CommonModes下的消息源
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
        struct _block_item *_blocks_head;  //需要处理的block的头节点
        struct _block_item *_blocks_tail;
        CFAbsoluteTime _sleepTime;
        ...
    };
    
    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    struct __CFRunLoopMode {
        ...
        pthread_mutex_t _lock;   
        CFStringRef _name;                                    //mode的名字
        Boolean _stopped;          
        char _padding[3];
        CFMutableSetRef _sources0;                  //mode中source0集合
        CFMutableSetRef _sources1;                //mode中source1集合
        CFMutableArrayRef _observers;         //mode中observers集合
        CFMutableArrayRef _timers;             //mode中timer集合    
        CFMutableDictionaryRef _portToV1SourceMap;  //所有的source1消息源对应的端口map
    /*本mode需要监听的端口集这个_portSet的创建是通过 rlm->_portSet = __CFPortSetAllocate();创建得到,__CFPortSetAllocate()的实现为。其是通过mach_port_allocate去分配得到端口的,注意其创建参数为MACH_PORT_RIGHT_PORT_SET得到了端口集合。
    CF_INLINE __CFPortSet __CFPortSetAllocate(void) {
        __CFPortSet result;
        kern_return_t ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &result);
        if (KERN_SUCCESS != ret) { __THE_SYSTEM_HAS_NO_PORT_SETS_AVAILABLE__(ret); }
        return (KERN_SUCCESS == ret) ? result : CFPORT_NULL;
    }*/
        __CFPortSet _portSet;
        CFIndex _observerMask;
    #if USE_DISPATCH_SOURCE_FOR_TIMERS    //用户态定时器
        dispatch_source_t _timerSource;         //这个_timetSource是在触发时间向_queue队列发送msg
        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
        uint64_t _timerSoftDeadline; /* TSR */ 
        uint64_t _timerHardDeadline; /* TSR */
    };
    
    struct __CFRunLoopSource {
           ...
        pthread_mutex_t _lock;
        CFIndex _order;           
        CFMutableBagRef _runLoops;
        union {
            CFRunLoopSourceContext version0;        //source0类型消息源
            CFRunLoopSourceContext1 version1;    //source1类型消息源
        } _context;                                                 //_context中存储了消息源的要执行的函数等一些状态信息。对于Source1类型消息源还在其info字段中保存了消息源的端口信息
    };
    
    struct __CFRunLoopObserver {
        ...
        CFRunLoopRef _runLoop;
        CFIndex _rlCount;
        CFOptionFlags _activities;                           //Observer类型比如kCFRunLoopBeforeTimers
        CFIndex _order;           
        CFRunLoopObserverCallBack _callout;   //本oberver执行的回调函数
        CFRunLoopObserverContext _context;   
    };
    
    struct __CFRunLoopTimer {
           ...
        CFRunLoopRef _runLoop;
        CFMutableSetRef _rlModes;
        CFAbsoluteTime _nextFireDate;                   //定时器下次触发时间
        CFTimeInterval _interval;                            //定时器间隔
        CFTimeInterval _tolerance;         
        uint64_t _fireTSR;                                     //定时器本次的触发时间
        CFIndex _order;           
        CFRunLoopTimerCallBack _callout;   //定时器触发调用的函数
        CFRunLoopTimerContext _context;    
    };
    
    • 通过上述数据结构可以很明显的看到每个Runloop中包含了很多Mode,模式存在_modes集合中。如果mode是commonMode,则mode的名字被入到了_commonModes中。主线程的_commonModes中包含了kCFRunLoopDefaultMode和UITrackingRunLoopMode。
    • 每个source/timer/Observer又称为modeitem,在每个mode中存储了source/timer/Observer集合,可以看到_sources0是source0集合,_sources1是source1集合_observers是observer集合,_timers是timer集合等。
    • _commonModeItems也存储了source,observer,timer,每次runloop运行时这些commonmodeItem都会被同步到具有Common标价的Modes中。
    • CFRunLoopModeRef。每次启动RunLoop时,只能指定其中一个 Mode,这个就是CurrentMode。要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。


      runloopmode.png
    ModeItem
    • source:
      source0:非端口源,其不能通过端口唤醒消息,在runloop每次循环中全部处理_sources0中的所有非端口源,使其执行对应的函数。performSelectoronMainThread:或者performSelector:OnThread:创建的都是source0类型的源。performSelector:不加入runloop中,直接执行。
      Source1:端口源,这类消息源在其_context中保存了消息源的端口,可以通过端口唤醒runloop循环。在CFMessagePort.c文件的 CFMessagePortSendRequest函数中看到了对source1消息的创建处理和发送具体的:

        SInt32 CFMessagePortSendRequest(CFMessagePortRef remote, SInt32 msgid, CFDataRef data, CFTimeInterval sendTimeout, CFTimeInterval rcvTimeout, CFStringRef replyMode, CFDataRef *returnDatap) {
      ...
      mach_msg_base_t *sendmsg;
      CFRunLoopSourceRef source = NULL;
      CFDataRef reply = NULL;
      kern_return_t ret;
      __CFMessagePortLock(remote);
      //创建了source1的应答端口
      if (NULL == remote->_replyPort) {
      CFMachPortContext context;
      context.version = 0;
      context.info = remote;
      context.retain = (const void *(*)(const void *))CFRetain;
      context.release = (void (*)(const void *))CFRelease;
      context.copyDescription = (CFStringRef (*)(const void *))__CFMessagePortCopyDescription;
      remote->_replyPort = CFMachPortCreate(CFGetAllocator(remote), __CFMessagePortReplyCallBack, &context, NULL);
      }
      …
      //创建要发送的消息,这个消息包含了应答端口,runloop需要监听这个端口来监听接下来创建的source1
      sendmsg = __CFMessagePortCreateMessage(false, CFMachPortGetPort(remote->_port), (replyMode != NULL ? CFMachPortGetPort(remote->_replyPort) : MACH_PORT_NULL), -desiredReply, msgid, (data ? CFDataGetBytePtr(data) : NULL), (data ? CFDataGetLength(data) : -1));
        ...
      if (replyMode != NULL) {
          CFDictionarySetValue(remote->_replies, (void *)(uintptr_t)desiredReply, NULL);
          //创建source1,这个函数中CFRunLoop.c中,这个函数中,将remote->_replyPort端口加入到了source1的上下文info中。具体的语句为: context.info = (void *)mp;mp为传进去的_replyPort
          source = CFMachPortCreateRunLoopSource(CFGetAllocator(remote), remote->_replyPort, -100);
          didRegister = !CFRunLoopContainsSource(currentRL, source, replyMode);
      if (didRegister) {
             // 将创建的source1加入到RunLoop的Mode中,在这个函数中将source1的端口加入到了mode中的portSet中具体的语句为:A代码           
                CFRunLoopAddSource(currentRL, source, replyMode);
      }
      }
      __CFMessagePortUnlock(remote);
      //向端口发送消息,其应答端口是刚刚创建的source1的info中的端口。runloop监听到这个端口有消息后做相应的处理
        ret = mach_msg((mach_msg_header_t *)sendmsg,   MACH_SEND_MSG|sendOpts, sendmsg->header.msgh_size, 0, MACH_PORT_NULL, sendTimeOut, MACH_PORT_NULL);
      __CFMessagePortLock(remote);
      ...
      return kCFMessagePortSuccess;
      }
      

      A代码:将source1上下文info端口加入到runloop的mode的监听端口集合中。

      __CFPort src_port = rls->_context.version1.getPort(rls->[_context.version1.info](http://_context.version1.info/));
      
          if(CFPORT_NULL != src_port) {
      
              CFDictionarySetValue(rlm->_portToV1SourceMap, (constvoid*)(uintptr_t)src_port, rls);
      
              __CFPortSetInsert(src_port, rlm->_portSet);
      
          }
      
      

      Source1和Timer都属于端口事件源,不同的是所有的Timer都共用一个端口(Timer Port或则queue对应的端口),而每个Source1都有不同的对应端口。
      Source0属于input Source中的一部分,Input Source还包括cuntom自定义源,由其他线程手动发出。

    • Observer
      CFRunLoopObserverRef观察者,监听runloop的状态。它不属于runloop的事件源。
       typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
                kCFRunLoopEntry = (1UL << 0),
                kCFRunLoopBeforeTimers = (1UL << 1),
                kCFRunLoopBeforeSources = (1UL << 2),
                kCFRunLoopBeforeWaiting = (1UL << 5),
                kCFRunLoopAfterWaiting = (1UL << 6),
                kCFRunLoopExit = (1UL << 7),
                kCFRunLoopAllActivities = 0x0FFFFFFFU
            };
      
      其中kCFRunLoopEntry对应的回调函数会创建aotureleasepool kCFRunLoopBeforeWaiting会释放自动释放池,kCFRunLoopAfterWaiting会创建自动释放池。

    三、MachPort实现线程通信(据说可以实现进程间的通信,能力有限没有实现,希望有同学能给出实现方案)

    关于MachPort的基本概念在这篇文章中有很好的描述,这里不再描述。我在下述代码中实现了两个线程之间通过machPort实现通信。

    #import <Foundation/Foundation.h>
    #import <CoreFoundation/CFMessagePort.h>
    #import <CoreFoundation/CFMachPort.h>
    #import <mach/mach.h>
    #import <mach/mach_port.h>
    #include <mach/clock_types.h>
    #include <mach/clock.h>
    #include <mach/message.h>
    #include <bootstrap.h>
    #define kMachPortConfigd "com.apple.SystemConfiguration.configd"
    struct send_body {
        mach_msg_header_t header; int count; UInt8 *addr; CFIndex size0; int flags; NDR_record_t ndr; CFIndex size; int retB; int rcB; int f24; int f28;
        
    };
    int main(int argc, const char * argv[]) {
        @autoreleasepool {  
    //发送remote端口和应答local端口不一致
            mach_port_t bp,sendPort;
            task_get_bootstrap_port(mach_task_self(), &bp);
            kern_return_t ret = bootstrap_look_up2(bp,kMachPortConfigd, &sendPort, 0,8LL);
            assert(ret == KERN_SUCCESS);
            mach_port_name_t recPort;
            mach_msg_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &recPort);
            assert(kr == KERN_SUCCESS);
            kr = mach_port_insert_right(mach_task_self(), recPort, recPort, MACH_MSG_TYPE_MAKE_SEND);
            assert(kr == KERN_SUCCESS);
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            dispatch_async(queue, ^{
                uint8_t msg_buffer[3 * 1024];
                mach_msg_header_t *msg = (mach_msg_header_t *)msg_buffer;
                msg->msgh_remote_port = MACH_PORT_NULL;
                msg->msgh_size = sizeof(mach_msg_header_t);
                msg->msgh_bits =  0;
                //在接受端,msgh_local_port表示接受来自哪个端口的消息
                msg->msgh_local_port = recPort;
                mach_msg_timeout_t timeout = 300000;
                while (1) {
                    //mach_msg_return_t mr = mach_msg(msg, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(msg_buffer), recPort, timeout, MACH_PORT_NULL);
                    mach_msg_return_t mr = mach_msg_receive(msg);
                    if (mr != MACH_MSG_SUCCESS && mr != MACH_RCV_TOO_LARGE) {
                        printf("errorrec %x",mr);
                    }
                    printf("sucessful %d....\n",recPort);
                }
            });
            int I;
            while ( scanf("%d",&i)) {
                NSString *key = @"hello";
                struct send_body send_msg;
                CFDataRef  extRepr;
                extRepr = CFStringCreateExternalRepresentation(NULL, (__bridge CFStringRef)(key), kCFStringEncodingUTF8, 0);
                send_msg.count = 1;
                send_msg.addr = (UInt8*)CFDataGetBytePtr(extRepr);
                send_msg.size0 = CFDataGetLength(extRepr);
                send_msg.size = CFDataGetLength(extRepr);
                send_msg.flags = 0x1000100u;
                send_msg.ndr = NDR_record;
                mach_msg_header_t* msgs = &(send_msg.header);
                msgs->msgh_id = 100;
                //msgh_remote_port 目的端口,消息发送到指定的端口上
                msgs->msgh_bits =  MACH_MSGH_BITS((MACH_MSG_TYPE_MOVE_SEND | MACH_MSG_TYPE_COPY_SEND), MACH_MSG_TYPE_MAKE_SEND_ONCE);//MACH_MSG_TYPE_MAKE_SEND_ONCE回答告诉写端端口我能收到数据了
                //msgs->msgh_bits = 0x80001513u;
                msgs->msgh_remote_port = sendPort;
                msgs->msgh_size = sizeof(mach_msg_header_t);
                msgs->msgh_local_port = recPort;
                mach_msg_return_t mrs = mach_msg(msgs, MACH_SEND_MSG, sizeof(send_msg), sizeof(send_msg), msgs->msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
                //mach_msg_return_t mrs = mach_msg_send(&msgs);
                if (mrs != MACH_MSG_SUCCESS && mrs != MACH_RCV_TOO_LARGE) {
                    printf("errorsend %x",mrs);
                }
            }
        }
        return 0;
    }
    
    • 在mach通信发送方有一个目的端口定义在发送方msgheader消息头的msgh_remote_port字段,一个应答端口定义在msgheader消息头的msgh_local_port字段。在接受方有一个监听端口定义在接受方msgheader消息头的msgh_local_port字段。

    端口的产生:

     mach_port_t bp,sendPort;
     task_get_bootstrap_port(mach_task_self(), &bp);
     kern_return_t ret = bootstrap_look_up2(bp,kMachPortConfigd, &sendPort, 0,8LL);
    

    应答端口的产生

    mach_msg_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &recPort);
    

    曾经试过通过这种方式产生发送方的目的端口但不能实现通信,原因未知。

    发送方msg的mach_msg_header_t设置

    msgs->msgh_bits =  MACH_MSGH_BITS((MACH_MSG_TYPE_MOVE_SEND | MACH_MSG_TYPE_COPY_SEND),                    MACH_MSG_TYPE_MAKE_SEND_ONCE);//MACH_MSG_TYPE_MAKE_SEND_ONCE
    

    回答告诉写端端口我能收到数据了。

      msgs->msgh_remote_port = sendPort;
      msgs->msgh_size = sizeof(mach_msg_header_t);
      msgs->msgh_local_port = recPort;
    

    苹果官方文档中说msgh_remote_port是目的端口,msgh_local_port是应答端口。MACH_MSGH_BITS函数设置了目的端口和应答端口的权限,第一个参数是设置目的端口的权限,第二个是设置应答端口的权限。这里设置目的端口MACH_MSG_TYPE_MOVE_SEND,应答端口是MACH_MSG_TYPE_MAKE_SEND_ONCE,至于MACH_MSG_TYPE_MAKE_SEND_ONCE的含义,猜测的意思是MACH_MSG_TYPE_MAKE_SEND_ONCE回答告诉写端端口我能收到数据了。

    接收方msgheader设置
    接受方设置自己的msgs时设置msgh_local_port也为发送方刚才设置的应答端口msgh_local_port的值便可以实现对端口消息的监听。刚开始我想如果设置发送方的msgh_remote_port目的端口和接受方msgh_local_port应答端口一致假设都是report,发送方msgh_local_port设置null,接受方msgh_remote_port设置为null,能不能实现通信呢,经过测试完全可以通信。这种方式是发送方向recport发送数据,接受方去监听recport端口。但是发送方目的端口和发送方应答端口不一致又是怎么实现通信的呢,我猜測可能是这样的一种模型:

    端口通信模型.png

    发送方在设置了msgs的msgh_remote_port和msgh_local_port后,这两个端口可以实现通信,并且是一种全双工通信,当向msgh_remote_port目的端口写数据时候,应答端口msgh_local_port可以读到。所以接受方去监听应答端口可以得到应答端口的数据。

    关于创建目的端口在CFMessagePort.c文件中的 __CFMessagePortCreateRemote创建远程端口中找见,至于为什么这么实现我页不清楚希望有同学告诉我。

    task_get_bootstrap_port(mach_task_self(), &bp);
    kern_return_t ret = bootstrap_look_up2(bp,kMachPortConfigd, &sendPort, 0,8LL);
    

    四、RunLoop实现

    4.1、_CFRunLoopRun下边代码是RunLoop运行部分的代码(关于runloop的解释,我已注释方式给出)

      static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    //获取当前时间
        uint64_t startTSR = mach_absolute_time();
    //判断当前runloop或者runloop当前运行的mode是否已经停止,如果已经停止则返回。
        if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            return kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            return kCFRunLoopRunStopped;
        }
    //主线程队列端口这个端口存放了获取主线程队列的消息队列,也就是从其他线程切换回主线程时发的消息
        mach_port_name_t dispatchPort = MACH_PORT_NULL;
    //当前线程是主线程且当前Runloop是主线程runloop且当前模式是被标记为_common模式时,获取主线程队列端口赋予dispatchPort中
        Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
        if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
    //如果定义了对timer的处理,这个处理是专门针对用户创建的Timer发送的消息
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
    /*端口,这个端口是rlm模式队列对应的端口,这个端口的消息队列中存储了发送到此端口的消息源,具体的是一些用户timer消息源。这个消息队列的创建可以在__CFRunLoopFindMode中看到 rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);  mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);且在这里创建了timerSource rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);并最后把queuePort端口加入了rlm的PortSet集合中ret = __CFPortSetInsert(queuePort, rlm->_portSet);*/
        mach_port_name_t modeQueuePort = MACH_PORT_NULL;
        if (rlm->_queue) {
            modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
            if (!modeQueuePort) {
                CRASH("Unable to get port for run loop mode queue (%d)", -1);
            }
        }
    #endif
    //创建对于这个RunLoop超时的计时器,其超时时间是传进来的参数secends,当过了secends时定时器会向rl的_wakeUpPort发超时msg,监听这个端口的线程被唤醒线程做超时处理具体的代码在  CFRunLoopWakeUp(context->rl);的 __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);
        dispatch_source_t timeout_timer = NULL;
        struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
        if (seconds <= 0.0) { // instant timeout
            seconds = 0.0;
            timeout_context->termTSR = 0ULL;
        } else if (seconds <= TIMER_INTERVAL_LIMIT) {
            dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
            timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
            dispatch_retain(timeout_timer);
            timeout_context->ds = timeout_timer;
            timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        //设置触发时间
            timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
            dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
            dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
            dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
            uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
            dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
            dispatch_resume(timeout_timer);
        } else { // infinite timeout
            seconds = 9999999999.0;
            timeout_context->termTSR = UINT64_MAX;
        }
    //上次是否有处理主线程队列端口事件。这个参数用于优先去处理对主线程队列端口监听得到的事件源
        Boolean didDispatchPortLastTime = true;
    //是否需要退出返回的条件
        int32_t retVal = 0;
        do {
    //构建接受msg消息的消息头
            uint8_t msg_buffer[3 * 1024];
            mach_msg_header_t *msg = NULL;
            mach_port_t livePort = MACH_PORT_NULL;
            __CFPortSet waitSet = rlm->_portSet;
            __CFRunLoopUnsetIgnoreWakeUps(rl);
    //处理Observers,对所有的Observers进行遍历如果有满足条件的比如是kCFRunLoopBeforeTimers则执行这个Observers对应的回调函数。
    //具体实现可以跳过去看:   
    /*for (CFIndex idx = 0; idx < cnt; idx++) {
            CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
            if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) {
                collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo);
            }
        }
    遍历observers,如果按位与不为0则将这个observer加入的集合collectedObservers中,接下来会对这个集合的observer调用其回调函数对activities的定义如下:  typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
                kCFRunLoopEntry = (1UL << 0),
                kCFRunLoopBeforeTimers = (1UL << 1),
                kCFRunLoopBeforeSources = (1UL << 2),
                kCFRunLoopBeforeWaiting = (1UL << 5),
                kCFRunLoopAfterWaiting = (1UL << 6),
                kCFRunLoopExit = (1UL << 7),
                kCFRunLoopAllActivities = 0x0FFFFFFFU
            };
    所以只有它自身与自身按位与或者与kCFRunLoopAllActivities按位与可以得到真*/
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    //处理rlm中的blocks(可能是加入线程中的blocks?)
            __CFRunLoopDoBlocks(rl, rlm);
    //处理rlm中所有的source0类型的消息源
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                __CFRunLoopDoBlocks(rl, rlm);
            }
    //是否处理了source0类型的消息源或则超时时间==0
            Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
    //如果主线程队列端口存在且上次没有处理主线程队列端口的msg则处理主线程队列端口的msg,及优先处理主线程队列的msg。
            if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
                msg = (mach_msg_header_t *)msg_buffer;
    //在__CFRunLoopServiceMachPort函数中构建了msg消息头,这个mach_msg_header_t的local_port是dispatchPort,然后使用mach_msg函数去监听dispatchPort是否有消息。如果没有进入休眠,如果有消息被唤醒或者有消息则返回。返回后直接跳转到handle_msg去处理msg。
                if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                    goto handle_msg;
                }
            }
            
            didDispatchPortLastTime = false;
    //如果上次没有处理source0类型消息源则调用处理所有观察者函数,回调处于kCFRunLoopBeforeWaiting或者和kCFRunLoopBeforeWaiting按位与为1的观察者的回调函数
            if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            __CFRunLoopSetSleeping(rl);
    //将主线程队列端口加入到监听端口集合中
            __CFPortSetInsert(dispatchPort, waitSet);
            
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
           
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
    /*当定义了DISPATCH_SOURCE_FOR_TIMERS既处理用户态定时器时候则去监听waitSet端口集合中的msg,当有端口有消息时则如果处于休眠则唤醒。如果没有消息则进程休眠。如果唤醒端口不是modeQueuePort端口(用户定时器消息源发送msg消息的端口)则跳出,如果是则遍历rlm->queue端口消息队列的所有msg,如果有一个msg是rlm的timerSource触发的,即用户定时器到时触发的,则其rlm->_timeFired会被置为yes(具体实现在 __CFRunLoopFindMode中的__block Boolean *timerFiredPointer = &(rlm->_timerFired);
        dispatch_source_set_event_handler(rlm->_timerSource, ^{
            *timerFiredPointer = true;
        });)跳出循环,并同时清空了消息队列中的所有msg。*/
            do {
                if (kCFUseCollectableAllocator) {
                    memset(msg_buffer, 0, sizeof(msg_buffer));
                }
                msg = (mach_msg_header_t *)msg_buffer;
    //__CFRunLoopServiceMachPort函数中设置了需要msg消息头并设置了msg.localPort为waitSet,即需要监听的端口集合,并mach_msg去接受监听端口集合
                __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.
                    //得到是否有把_timerFired置为true的msg,同时清空消息队列
                    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 {
    //如果唤醒端口不是modeQueuePort端口(用户定时器消息源发送msg消息的端口)则跳出
                    break;
                }
            } while (1);
    #else
    //如果没有定义USE_DISPATCH_SOURCE_FOR_TIMERS
            if (kCFUseCollectableAllocator) {
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    #endif
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            
            rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
            
            __CFPortSetRemove(dispatchPort, waitSet);
    //忽略端口唤醒msg
            __CFRunLoopSetIgnoreWakeUps(rl);
            
            __CFRunLoopUnsetSleeping(rl);
    //通知observers线程被唤醒了
            if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    //处理端口消息
        handle_msg:;
    //设置忽略端口唤醒消息
            __CFRunLoopSetIgnoreWakeUps(rl);
    //处理事件
            if (MACH_PORT_NULL == livePort) {
                CFRUNLOOP_WAKEUP_FOR_NOTHING();
                // handle nothing
            } 
    //struct __CFRunLoop中有这么一项:__CFPort _wakeUpPort,用于手动将当前runloop线程唤醒,通过调用CFRunLoopWakeUp完成,CFRunLoopWakeUp会向_wakeUpPort发送一条消息
    else if (livePort == rl->_wakeUpPort) {
                CFRUNLOOP_WAKEUP_FOR_WAKEUP();
                // do nothing on Mac OS
    #if DEPLOYMENT_TARGET_WINDOWS
                // Always reset the wake up port, or risk spinning forever
                ResetEvent(rl->_wakeUpPort);
    #endif
            }
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
    //如果是用户创建的timer如果消息端口是modeQueuePort则处理所有的timers定时器。具体的定时器处理逻辑在3描述
            else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer, because we apparently fired early
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
    #endif
    #if USE_MK_TIMER_TOO
    //如果是内核定时器
            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);
                }
            }
    #endif
    //如果是主线程队列端口
            else if (livePort == dispatchPort) {
                CFRUNLOOP_WAKEUP_FOR_DISPATCH();
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
    //9.2处理异步方法唤醒。处理gcd dispatch到main_queue的block,执行block。/*有判断是否是在MainRunLoop,有获取Main_Queue 的port,并且有调用 Main_Queue 上的回调,这只能是是 GCD 主队列上的异步任务。即:dispatch_async(dispatch_get_main_queue(), block)产生的任务。
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
                __CFRunLoopLock(rl);
                __CFRunLoopModeLock(rlm);
                sourceHandledThisLoop = true;
                didDispatchPortLastTime = true;
            } else {
    //处理source1消息源
                CFRUNLOOP_WAKEUP_FOR_SOURCE();
                voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
    //通过响应的端口得到这个端口对应的消息源。前文有说过source1消息源,一个消息源会在其context.info中包含一个应答端口
                CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
                if (rls) {
    //处理消息源
                    mach_msg_header_t *reply = NULL;
                    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
    //处理完source1给发消息的线程发送反馈
                    if (NULL != reply) {
                        (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                        CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                    }
                }
                
                // Restore the previous voucher
                _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
                
            }
            if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
    //处理blocks
            __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;
            }
            voucher_mach_msg_revert(voucherState);
            os_release(voucherCopy);
        } while (0 == retVal);
        
        if (timeout_timer) {
            dispatch_source_cancel(timeout_timer);
            dispatch_release(timeout_timer);
        } else {
            free(timeout_context);
        }
        
        return retVal;
    }
    

    由此可以做如下的归纳总结:

    • 一个消息队列对应一个端口,一个消息队列对应一个 dispatch_source_t类型的TimerSource源。这类型源会在触发时间给端口发送msg消息并触发设置的回调事件。如:上述代码中的
    dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : 
        __CFDispatchQueueGetGenericBackground();
    timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        和
    rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);
    mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***", -1);
    rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);
    
    • 对于Runloop的处理逻辑为:
    runloop处理逻辑.png

    4.2、RunLoop对用户创建timer的处理逻辑

    timer触发与处理.png

    代码timenextFire:

     nextFireTSR = oldFireTSR;//oldFireTSR是保存的上次刚刚被触发的时间
     while (nextFireTSR <= currentTSR) {
            nextFireTSR += intervalTSR;
     }
    

    上图是runLoop对Timer的处理逻辑。以下是每个部分的在RunLoop的源码及解释:

     rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);
     mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
     rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);
    

    由上述代码可以看到dispatch_source_t时间消息源在创建的时候和消息队列绑定在一起。dipatch_source的触发时间通过7得到,当触发 时间到达时,_timerSource会自动向消息队列对应的端口发消息(_timerSource自动向消息队列对应的端口发消息的代码可能在内核中实现,没有找到?),消息被存储在消息队列中。

    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    

    上述代码监听端口集合的消息源,端口集合包含了_timerSource所要发送的消息端口modeQueuePort。具体实现在RunLoop的do..while(retval)循环中的专门对modeQueuePort监听处理的do..while(1)中。

    1. 同上。当端口集合中没有任何消息时候,线程会自动休眠,当有消息时会唤醒休眠线程。
    static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {
        Boolean timerHandled = false;
        CFMutableArrayRef timers = NULL;
        for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
            CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
            
            if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
                if (rlt->_fireTSR <= limitTSR) {
                    if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                    CFArrayAppendValue(timers, rlt);
                }
            }
        }
        
        for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
            CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
            Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
            timerHandled = timerHandled || did;
        }
        if (timers) CFRelease(timers);
        return timerHandled;
    }
    
    1. 当监听到modeQueuePort有消息时对modeQueue做处理,这部分代码在modeQueuePort==livePort中,调用了代码__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()。上述代码中可以看到__CFRunLoopDoTimers将所有rlt->_fireTSR <= limitTSR的timer放置在了即将要处理的timers中,即将所有触发时间小于当前时间的timer放置于timers中。
    2. __CFRunLoopDoTimers中看到,遍历timers集合中的元素做_CFRunLoopDoTimer(rl, rlm, rlt);
    3. 在_CFRunLoopDoTimer中首先将rlt设置为firing状态: __CFRunLoopTimerSetFiring(rlt);
    4. 然后去设置timerSource的下次触发时间。具体的代码在 __CFArmNextTimerInMode(rlm, rl);这部分代码没怎么明白。
    5. 接着调用timer的回调函数去处理timer的具体事件,并将firing状态取消。
      __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info); __CFRunLoopTimerUnsetFiring(rlt);
    6. timer处理完成后设置timer的下次触发时间。具体的实现在
     uint64_t currentTSR = mach_absolute_time();
               nextFireTSR = oldFireTSR;
               while (nextFireTSR <= currentTSR) {
                    nextFireTSR += intervalTSR;
               }
    

    获取当前时间currentTSR,获取下次触发时间nextFireTSR初始等于上次的触发时间oldFireTSR,oldFireTSR在进行timer处理前被赋过值。然后nextFireTSR每次加时间间隔直到其大于当前时间位置。然后在下边代码中有rlt->_fireTSR = nextFireTSR;rlt的下次触发时间完成。
    总结:

    • 由上述逻辑可以看到如果创建一个重复执行的tiemr,timer的周期是2s,但timer的执行时间是8s,则会忽略掉中间的4s,6s,直接触发8s的执行。
    • 如果创建两个循环timer,Atimer周期2s,执行时间8s,Btimer周期3s,执行时间8s,则在A的执行时间中B会到时,会触发_timerSource发送消息源到端口,当A执行完后会执行B,B执行完执行A。AB之间的有些时间点会忽略。经过测试这样的程序会差点把机子搞死。
    • 如果创建循环timerA,Atimer周期2s,执行时间8s,创建一次性TimerB,Btimer周期3s,则BTimer在A执行完成后才被执行。
    • 所以尽量timer的周期要大于执行timer事件需要的时间。
    • 如果有需要处理很长时间的timer,则在尽量timer的周期要大于执行timer事件需要的时间的基础上,把处理放在一个新的线程中,否则会造成界面卡顿。

    4.3 RunLoop对source的处理逻辑
    在函数 _CFRunLoopDoSources0处理了所有source0类型的消息,这个函数中对rum->_source0中的所有消息进行了遍历,对每个消息调用了 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION()函数去执行对应的source要执行的行为。每次处理多个。函数__CFRunLoopDoSource1处理的是监听到的端口对应的那个source1消息。每次只处理一个。

    4.4 RunLoop对observe的处理逻辑
    处理Observers,对所有的Observers进行遍历如果有满足条件的则执行这个Observers对应的回调函数。比如在调用 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers)时,在__CFRunLoopDoObservers会有

     for (CFIndex idx = 0; idx < cnt; idx++) {
            CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
            if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) {
                collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo);
            }
        }
    

    遍历observers,如果按位与不为0则将这个observer加入的集合collectedObservers中,接下来会对这个集合的observer调用其回调函数对activities的定义如下:

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
                kCFRunLoopEntry = (1UL << 0),
                kCFRunLoopBeforeTimers = (1UL << 1),
                kCFRunLoopBeforeSources = (1UL << 2),
                kCFRunLoopBeforeWaiting = (1UL << 5),
                kCFRunLoopAfterWaiting = (1UL << 6),
                kCFRunLoopExit = (1UL << 7),
                kCFRunLoopAllActivities = 0x0FFFFFFFU
            };
    

    所以只有它自身与自身按位与或者与kCFRunLoopAllActivities按位与可以得到真。

    IOS渲染与Runloop

    IOS CADisplayLink定时渲染的处理逻辑:

    runloop渲染处理逻辑.png

    假设当前屏幕在扫描A缓存内容,render正在向B缓存渲染内容。swapbuffer交换缓存函数会使线程监听一个端口,当这个端口有同步信号时被唤醒,没有时线程被阻塞。

    当屏幕扫描完成后发送sync同步信号,这时如果主线程如果已经处理了render之前的其他source源,已经进入了render回调且B缓存内容已经渲染完成,已经到达了交换缓存进入的阻塞态,则主线程会唤醒进行缓存交换,屏幕去使用B缓存更新内容,并使dispatch_source_t定时器触发其向runloop监听的端口modeQueue中发射msg消息。当runloop监听到的端口modeQueue时,开始处理所有的source0,小于等于当前时间的timer源,这包括rendertimer源,于是开始去进行渲染。重复这个过程。
    所以在这种情况下任何在主线程中有复杂的处理逻辑,或者在消息源中有复杂的处理逻辑都会造成丢帧,造成卡顿。

    一般的如果不是进行每帧都要渲染的逻辑时,比如在IOS中frame发生改变时才进行重新渲染时,向runLoop发送一个消息源,同时向swapeBuffer监听的端口发送sync同步信号,当runloop处理这个消息源时会调用渲染函数重新渲染,如果渲染逻辑过于复杂则不能很快的完成渲染,使得很长时间才到达缓存交换阶段,这样会造成卡顿。

    本文参考文章:

    官方文档

    关于Runloop的原理探究及基本使用

    Mach消息发送机制

    opengl SwapBuffers的等待,虚伪的FPS

    相关文章

      网友评论

        本文标题:RunLoop源码分析

        本文链接:https://www.haomeiwen.com/subject/rnxrsftx.html