RunLoop源碼分析

之前一直對ios的RunLoop機(jī)制一知半解,很多地方不是很清楚于是每次想到這個問題都會糾結(jié)悄谐,想搞明白這里邊到底做了一些什么事情们陆。最近一周時間稍微寬裕,終于抽出來一些時間去閱讀了一下CoreFoundation中的關(guān)于RunLoop的源碼椅文。可以在這里下載得到羡蛾。這里有很多版本忙干,我下載了CF-1151.16版本。

一弓乙、什么是RunLoop

關(guān)于RunLoop簡單的理解就是一層do-while循環(huán),在這層循環(huán)中不斷的去監(jiān)聽處理一些事件源,比如定時器事件涂乌、觸摸事件等,當(dāng)沒有任何事件可處理時罚勾,它就會去睡眠。當(dāng)然送丰,當(dāng)條件不滿足時比如超時或者主動退出等其他因素退出時會跳出這層循環(huán)。

CFRunLoop基于pthread來管理登失,主線程的runloop自動創(chuàng)建,子線程的runloop默認(rèn)不創(chuàng)建太抓。runLoop不通過alloc init來創(chuàng)建,要獲得runLoop可以通過函數(shù)CFRunLoopGetMain( )和CFRunLoopGetCurrent( )來獲得主線程掉丽、當(dāng)前線程的runloop(實質(zhì)是一種懶加載)。

二项炼、RunLoop相關(guān)數(shù)據(jù)結(jié)構(gòu)

 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的頭節(jié)點(diǎn)
    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消息源對應(yīng)的端口map
/*本mode需要監(jiān)聽的端口集這個_portSet的創(chuàng)建是通過 rlm->_portSet = __CFPortSetAllocate();創(chuàng)建得到拌禾,__CFPortSetAllocate()的實現(xiàn)為湃窍。其是通過mach_port_allocate去分配得到端口的循榆,注意其創(chuàng)建參數(shù)為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    //用戶態(tài)定時器
    dispatch_source_t _timerSource;         //這個_timetSource是在觸發(fā)時間向_queue隊列發(fā)送msg
    dispatch_queue_t _queue;                  //端口消息隊列,定時器在規(guī)定的觸發(fā)時間會向端口發(fā)射消息,消息被存儲在這個端口的消息隊列中
    Boolean _timerFired;                         // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO           //內(nèi)核態(tài)定時器
    mach_port_t _timerPort;            //內(nèi)核態(tài)定時器觸發(fā)后發(fā)出的消息源的應(yīng)答端口
    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中存儲了消息源的要執(zhí)行的函數(shù)等一些狀態(tài)信息及塘。對于Source1類型消息源還在其info字段中保存了消息源的端口信息
};

struct __CFRunLoopObserver {
    ...
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;                           //Observer類型比如kCFRunLoopBeforeTimers
    CFIndex _order;           
    CFRunLoopObserverCallBack _callout;   //本oberver執(zhí)行的回調(diào)函數(shù)
    CFRunLoopObserverContext _context;   
};

struct __CFRunLoopTimer {
       ...
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;                   //定時器下次觸發(fā)時間
    CFTimeInterval _interval;                            //定時器間隔
    CFTimeInterval _tolerance;         
    uint64_t _fireTSR;                                     //定時器本次的觸發(fā)時間
    CFIndex _order;           
    CFRunLoopTimerCallBack _callout;   //定時器觸發(fā)調(diào)用的函數(shù)
    CFRunLoopTimerContext _context;    
};
  • 通過上述數(shù)據(jù)結(jié)構(gòu)可以很明顯的看到每個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運(yùn)行時這些commonmodeItem都會被同步到具有Common標(biāo)價的Modes中距帅。
  • CFRunLoopModeRef绍移。每次啟動RunLoop時登夫,只能指定其中一個 Mode,這個就是CurrentMode。要切換 Mode,只能退出 Loop碳褒,再重新指定一個 Mode 進(jìn)入。


    runloopmode.png
ModeItem
  • source:
    source0:非端口源,其不能通過端口喚醒消息是复,在runloop每次循環(huán)中全部處理_sources0中的所有非端口源季惩,使其執(zhí)行對應(yīng)的函數(shù)关摇。performSelectoronMainThread:或者performSelector:OnThread:創(chuàng)建的都是source0類型的源些楣。performSelector:不加入runloop中愁茁,直接執(zhí)行蚕钦。
    Source1:端口源,這類消息源在其_context中保存了消息源的端口鹅很,可以通過端口喚醒runloop循環(huán)嘶居。在CFMessagePort.c文件的 CFMessagePortSendRequest函數(shù)中看到了對source1消息的創(chuàng)建處理和發(fā)送具體的:

      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);
    //創(chuàng)建了source1的應(yīng)答端口
    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);
    }
    …
    //創(chuàng)建要發(fā)送的消息,這個消息包含了應(yīng)答端口促煮,runloop需要監(jiān)聽這個端口來監(jiān)聽接下來創(chuàng)建的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);
        //創(chuàng)建source1,這個函數(shù)中CFRunLoop.c中,這個函數(shù)中宣渗,將remote->_replyPort端口加入到了source1的上下文info中每窖。具體的語句為: context.info = (void *)mp;mp為傳進(jìn)去的_replyPort
        source = CFMachPortCreateRunLoopSource(CFGetAllocator(remote), remote->_replyPort, -100);
        didRegister = !CFRunLoopContainsSource(currentRL, source, replyMode);
    if (didRegister) {
           // 將創(chuàng)建的source1加入到RunLoop的Mode中污秆,在這個函數(shù)中將source1的端口加入到了mode中的portSet中具體的語句為:A代碼           
              CFRunLoopAddSource(currentRL, source, replyMode);
    }
    }
    __CFMessagePortUnlock(remote);
    //向端口發(fā)送消息予弧,其應(yīng)答端口是剛剛創(chuàng)建的source1的info中的端口仅仆。runloop監(jiān)聽到這個端口有消息后做相應(yīng)的處理
      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的監(jiān)聽端口集合中。

    __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對應(yīng)的端口)赌结,而每個Source1都有不同的對應(yīng)端口穴店。
    Source0屬于input Source中的一部分呕诉,Input Source還包括cuntom自定義源亦渗,由其他線程手動發(fā)出。

  • Observer
    CFRunLoopObserverRef觀察者昔搂,監(jiān)聽runloop的狀態(tài)锁荔。它不屬于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對應(yīng)的回調(diào)函數(shù)會創(chuàng)建aotureleasepool kCFRunLoopBeforeWaiting會釋放自動釋放池,kCFRunLoopAfterWaiting會創(chuàng)建自動釋放池。

三、MachPort實現(xiàn)線程通信(據(jù)說可以實現(xiàn)進(jìn)程間的通信示血,能力有限沒有實現(xiàn),希望有同學(xué)能給出實現(xiàn)方案)

關(guān)于MachPort的基本概念在這篇文章中有很好的描述,這里不再描述隘冲。我在下述代碼中實現(xiàn)了兩個線程之間通過machPort實現(xiàn)通信厕宗。

#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 {  
//發(fā)送remote端口和應(yīng)答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 目的端口,消息發(fā)送到指定的端口上
            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回答告訴寫端端口我能收到數(shù)據(jù)了
            //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通信發(fā)送方有一個目的端口定義在發(fā)送方msgheader消息頭的msgh_remote_port字段烛卧,一個應(yīng)答端口定義在msgheader消息頭的msgh_local_port字段局雄。在接受方有一個監(jiān)聽端口定義在接受方msgheader消息頭的msgh_local_port字段。

端口的產(chǎn)生:

 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);

應(yīng)答端口的產(chǎn)生

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

曾經(jīng)試過通過這種方式產(chǎn)生發(fā)送方的目的端口但不能實現(xiàn)通信审丘,原因未知。

發(fā)送方msg的mach_msg_header_t設(shè)置

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

回答告訴寫端端口我能收到數(shù)據(jù)了桩警。

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

蘋果官方文檔中說msgh_remote_port是目的端口可训,msgh_local_port是應(yīng)答端口。MACH_MSGH_BITS函數(shù)設(shè)置了目的端口和應(yīng)答端口的權(quán)限捶枢,第一個參數(shù)是設(shè)置目的端口的權(quán)限握截,第二個是設(shè)置應(yīng)答端口的權(quán)限。這里設(shè)置目的端口MACH_MSG_TYPE_MOVE_SEND烂叔,應(yīng)答端口是MACH_MSG_TYPE_MAKE_SEND_ONCE谨胞,至于MACH_MSG_TYPE_MAKE_SEND_ONCE的含義,猜測的意思是MACH_MSG_TYPE_MAKE_SEND_ONCE回答告訴寫端端口我能收到數(shù)據(jù)了蒜鸡。

接收方msgheader設(shè)置
接受方設(shè)置自己的msgs時設(shè)置msgh_local_port也為發(fā)送方剛才設(shè)置的應(yīng)答端口msgh_local_port的值便可以實現(xiàn)對端口消息的監(jiān)聽胯努。剛開始我想如果設(shè)置發(fā)送方的msgh_remote_port目的端口和接受方msgh_local_port應(yīng)答端口一致假設(shè)都是report,發(fā)送方msgh_local_port設(shè)置null,接受方msgh_remote_port設(shè)置為null逢防,能不能實現(xiàn)通信呢叶沛,經(jīng)過測試完全可以通信。這種方式是發(fā)送方向recport發(fā)送數(shù)據(jù)忘朝,接受方去監(jiān)聽recport端口灰署。但是發(fā)送方目的端口和發(fā)送方應(yīng)答端口不一致又是怎么實現(xiàn)通信的呢,我猜測可能是這樣的一種模型:

端口通信模型.png

發(fā)送方在設(shè)置了msgs的msgh_remote_port和msgh_local_port后辜伟,這兩個端口可以實現(xiàn)通信氓侧,并且是一種全雙工通信,當(dāng)向msgh_remote_port目的端口寫數(shù)據(jù)時候导狡,應(yīng)答端口msgh_local_port可以讀到约巷。所以接受方去監(jiān)聽?wèi)?yīng)答端口可以得到應(yīng)答端口的數(shù)據(jù)。

關(guān)于創(chuàng)建目的端口在CFMessagePort.c文件中的 __CFMessagePortCreateRemote創(chuàng)建遠(yuǎn)程端口中找見旱捧,至于為什么這么實現(xiàn)我頁不清楚希望有同學(xué)告訴我独郎。

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

四踩麦、RunLoop實現(xiàn)

4.1、_CFRunLoopRun下邊代碼是RunLoop運(yùn)行部分的代碼(關(guān)于runloop的解釋氓癌,我已注釋方式給出)

  static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//獲取當(dāng)前時間
    uint64_t startTSR = mach_absolute_time();
//判斷當(dāng)前runloop或者runloop當(dāng)前運(yùn)行的mode是否已經(jīng)停止谓谦,如果已經(jīng)停止則返回。
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
//主線程隊列端口這個端口存放了獲取主線程隊列的消息隊列贪婉,也就是從其他線程切換回主線程時發(fā)的消息
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
//當(dāng)前線程是主線程且當(dāng)前Runloop是主線程runloop且當(dāng)前模式是被標(biāo)記為_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的處理,這個處理是專門針對用戶創(chuàng)建的Timer發(fā)送的消息
#if USE_DISPATCH_SOURCE_FOR_TIMERS
/*端口疲迂,這個端口是rlm模式隊列對應(yīng)的端口挟裂,這個端口的消息隊列中存儲了發(fā)送到此端口的消息源糕珊,具體的是一些用戶timer消息源贝搁。這個消息隊列的創(chuàng)建可以在__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);且在這里創(chuàng)建了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
//創(chuàng)建對于這個RunLoop超時的計時器萍嬉,其超時時間是傳進(jìn)來的參數(shù)secends,當(dāng)過了secends時定時器會向rl的_wakeUpPort發(fā)超時msg腰池,監(jiān)聽這個端口的線程被喚醒線程做超時處理具體的代碼在  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);
    //設(shè)置觸發(fā)時間
        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;
    }
//上次是否有處理主線程隊列端口事件尾组。這個參數(shù)用于優(yōu)先去處理對主線程隊列端口監(jiān)聽得到的事件源
    Boolean didDispatchPortLastTime = true;
//是否需要退出返回的條件
    int32_t retVal = 0;
    do {
//構(gòu)建接受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進(jìn)行遍歷如果有滿足條件的比如是kCFRunLoopBeforeTimers則執(zhí)行這個Observers對應(yīng)的回調(diào)函數(shù)示弓。
//具體實現(xiàn)可以跳過去看:   
/*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調(diào)用其回調(diào)函數(shù)對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,及優(yōu)先處理主線程隊列的msg拍皮。
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
//在__CFRunLoopServiceMachPort函數(shù)中構(gòu)建了msg消息頭,這個mach_msg_header_t的local_port是dispatchPort跑杭,然后使用mach_msg函數(shù)去監(jiān)聽dispatchPort是否有消息铆帽。如果沒有進(jìn)入休眠,如果有消息被喚醒或者有消息則返回德谅。返回后直接跳轉(zhuǎn)到handle_msg去處理msg爹橱。
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }
        
        didDispatchPortLastTime = false;
//如果上次沒有處理source0類型消息源則調(diào)用處理所有觀察者函數(shù),回調(diào)處于kCFRunLoopBeforeWaiting或者和kCFRunLoopBeforeWaiting按位與為1的觀察者的回調(diào)函數(shù)
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
//將主線程隊列端口加入到監(jiān)聽端口集合中
        __CFPortSetInsert(dispatchPort, waitSet);
        
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
       
#if USE_DISPATCH_SOURCE_FOR_TIMERS
/*當(dāng)定義了DISPATCH_SOURCE_FOR_TIMERS既處理用戶態(tài)定時器時候則去監(jiān)聽waitSet端口集合中的msg窄做,當(dāng)有端口有消息時則如果處于休眠則喚醒愧驱。如果沒有消息則進(jìn)程休眠。如果喚醒端口不是modeQueuePort端口(用戶定時器消息源發(fā)送msg消息的端口)則跳出椭盏,如果是則遍歷rlm->queue端口消息隊列的所有msg组砚,如果有一個msg是rlm的timerSource觸發(fā)的,即用戶定時器到時觸發(fā)的掏颊,則其rlm->_timeFired會被置為yes(具體實現(xiàn)在 __CFRunLoopFindMode中的__block Boolean *timerFiredPointer = &(rlm->_timerFired);
    dispatch_source_set_event_handler(rlm->_timerSource, ^{
        *timerFiredPointer = true;
    });)跳出循環(huán)糟红,并同時清空了消息隊列中的所有msg艾帐。*/
        do {
            if (kCFUseCollectableAllocator) {
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
//__CFRunLoopServiceMachPort函數(shù)中設(shè)置了需要msg消息頭并設(shè)置了msg.localPort為waitSet,即需要監(jiān)聽的端口集合盆偿,并mach_msg去接受監(jiān)聽端口集合
            __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端口(用戶定時器消息源發(fā)送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:;
//設(shè)置忽略端口喚醒消息
        __CFRunLoopSetIgnoreWakeUps(rl);
//處理事件
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } 
//struct __CFRunLoop中有這么一項:__CFPort _wakeUpPort,用于手動將當(dāng)前runloop線程喚醒事扭,通過調(diào)用CFRunLoopWakeUp完成捎稚,CFRunLoopWakeUp會向_wakeUpPort發(fā)送一條消息
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
//如果是用戶創(chuàng)建的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
//如果是內(nèi)核定時器
        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阳藻,執(zhí)行block。/*有判斷是否是在MainRunLoop谈撒,有獲取Main_Queue 的port腥泥,并且有調(diào)用 Main_Queue 上的回調(diào),這只能是是 GCD 主隊列上的異步任務(wù)啃匿。即:dispatch_async(dispatch_get_main_queue(), block)產(chǎn)生的任務(wù)蛔外。
            __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);
//通過響應(yīng)的端口得到這個端口對應(yīng)的消息源。前文有說過source1消息源溯乒,一個消息源會在其context.info中包含一個應(yīng)答端口
            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給發(fā)消息的線程發(fā)送反饋
                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;
}

由此可以做如下的歸納總結(jié):

  • 一個消息隊列對應(yīng)一個端口夹厌,一個消息隊列對應(yīng)一個 dispatch_source_t類型的TimerSource源。這類型源會在觸發(fā)時間給端口發(fā)送msg消息并觸發(fā)設(shè)置的回調(diào)事件裆悄。如:上述代碼中的
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對用戶創(chuàng)建timer的處理邏輯

timer觸發(fā)與處理.png

代碼timenextFire:

 nextFireTSR = oldFireTSR;//oldFireTSR是保存的上次剛剛被觸發(fā)的時間
 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時間消息源在創(chuàng)建的時候和消息隊列綁定在一起或南。dipatch_source的觸發(fā)時間通過7得到,當(dāng)觸發(fā) 時間到達(dá)時艾君,_timerSource會自動向消息隊列對應(yīng)的端口發(fā)消息(_timerSource自動向消息隊列對應(yīng)的端口發(fā)消息的代碼可能在內(nèi)核中實現(xiàn)采够,沒有找到?)冰垄,消息被存儲在消息隊列中蹬癌。

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

上述代碼監(jiān)聽端口集合的消息源,端口集合包含了_timerSource所要發(fā)送的消息端口modeQueuePort虹茶。具體實現(xiàn)在RunLoop的do..while(retval)循環(huán)中的專門對modeQueuePort監(jiān)聽處理的do..while(1)中逝薪。

  1. 同上。當(dāng)端口集合中沒有任何消息時候蝴罪,線程會自動休眠董济,當(dāng)有消息時會喚醒休眠線程。
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. 當(dāng)監(jiān)聽到modeQueuePort有消息時對modeQueue做處理洲炊,這部分代碼在modeQueuePort==livePort中感局,調(diào)用了代碼__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()尼啡。上述代碼中可以看到__CFRunLoopDoTimers將所有rlt->_fireTSR <= limitTSR的timer放置在了即將要處理的timers中,即將所有觸發(fā)時間小于當(dāng)前時間的timer放置于timers中询微。
  2. __CFRunLoopDoTimers中看到崖瞭,遍歷timers集合中的元素做_CFRunLoopDoTimer(rl, rlm, rlt);
  3. 在_CFRunLoopDoTimer中首先將rlt設(shè)置為firing狀態(tài): __CFRunLoopTimerSetFiring(rlt);
  4. 然后去設(shè)置timerSource的下次觸發(fā)時間。具體的代碼在 __CFArmNextTimerInMode(rlm, rl);這部分代碼沒怎么明白撑毛。
  5. 接著調(diào)用timer的回調(diào)函數(shù)去處理timer的具體事件书聚,并將firing狀態(tài)取消。
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info); __CFRunLoopTimerUnsetFiring(rlt);
  6. timer處理完成后設(shè)置timer的下次觸發(fā)時間藻雌。具體的實現(xiàn)在
 uint64_t currentTSR = mach_absolute_time();
           nextFireTSR = oldFireTSR;
           while (nextFireTSR <= currentTSR) {
                nextFireTSR += intervalTSR;
           }

獲取當(dāng)前時間currentTSR雌续,獲取下次觸發(fā)時間nextFireTSR初始等于上次的觸發(fā)時間oldFireTSR,oldFireTSR在進(jìn)行timer處理前被賦過值胯杭。然后nextFireTSR每次加時間間隔直到其大于當(dāng)前時間位置驯杜。然后在下邊代碼中有rlt->_fireTSR = nextFireTSR;rlt的下次觸發(fā)時間完成。
總結(jié):

  • 由上述邏輯可以看到如果創(chuàng)建一個重復(fù)執(zhí)行的tiemr,timer的周期是2s做个,但timer的執(zhí)行時間是8s鸽心,則會忽略掉中間的4s,6s,直接觸發(fā)8s的執(zhí)行居暖。
  • 如果創(chuàng)建兩個循環(huán)timer顽频,Atimer周期2s,執(zhí)行時間8s太闺,Btimer周期3s糯景,執(zhí)行時間8s,則在A的執(zhí)行時間中B會到時省骂,會觸發(fā)_timerSource發(fā)送消息源到端口蟀淮,當(dāng)A執(zhí)行完后會執(zhí)行B,B執(zhí)行完執(zhí)行A冀宴。AB之間的有些時間點(diǎn)會忽略灭贷。經(jīng)過測試這樣的程序會差點(diǎn)把機(jī)子搞死。
  • 如果創(chuàng)建循環(huán)timerA略贮,Atimer周期2s,執(zhí)行時間8s仗岖,創(chuàng)建一次性TimerB,Btimer周期3s,則BTimer在A執(zhí)行完成后才被執(zhí)行逃延。
  • 所以盡量timer的周期要大于執(zhí)行timer事件需要的時間。
  • 如果有需要處理很長時間的timer轧拄,則在盡量timer的周期要大于執(zhí)行timer事件需要的時間的基礎(chǔ)上揽祥,把處理放在一個新的線程中,否則會造成界面卡頓檩电。

4.3 RunLoop對source的處理邏輯
在函數(shù) _CFRunLoopDoSources0處理了所有source0類型的消息拄丰,這個函數(shù)中對rum->_source0中的所有消息進(jìn)行了遍歷府树,對每個消息調(diào)用了 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION()函數(shù)去執(zhí)行對應(yīng)的source要執(zhí)行的行為。每次處理多個料按。函數(shù)__CFRunLoopDoSource1處理的是監(jiān)聽到的端口對應(yīng)的那個source1消息奄侠。每次只處理一個。

4.4 RunLoop對observe的處理邏輯
處理Observers载矿,對所有的Observers進(jìn)行遍歷如果有滿足條件的則執(zhí)行這個Observers對應(yīng)的回調(diào)函數(shù)垄潮。比如在調(diào)用 __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調(diào)用其回調(diào)函數(shù)對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

假設(shè)當(dāng)前屏幕在掃描A緩存內(nèi)容牡整,render正在向B緩存渲染內(nèi)容。swapbuffer交換緩存函數(shù)會使線程監(jiān)聽一個端口溺拱,當(dāng)這個端口有同步信號時被喚醒逃贝,沒有時線程被阻塞。

當(dāng)屏幕掃描完成后發(fā)送sync同步信號盟迟,這時如果主線程如果已經(jīng)處理了render之前的其他source源秋泳,已經(jīng)進(jìn)入了render回調(diào)且B緩存內(nèi)容已經(jīng)渲染完成,已經(jīng)到達(dá)了交換緩存進(jìn)入的阻塞態(tài)攒菠,則主線程會喚醒進(jìn)行緩存交換迫皱,屏幕去使用B緩存更新內(nèi)容,并使dispatch_source_t定時器觸發(fā)其向runloop監(jiān)聽的端口modeQueue中發(fā)射msg消息辖众。當(dāng)runloop監(jiān)聽到的端口modeQueue時卓起,開始處理所有的source0,小于等于當(dāng)前時間的timer源,這包括rendertimer源凹炸,于是開始去進(jìn)行渲染戏阅。重復(fù)這個過程。
所以在這種情況下任何在主線程中有復(fù)雜的處理邏輯啤它,或者在消息源中有復(fù)雜的處理邏輯都會造成丟幀奕筐,造成卡頓。

一般的如果不是進(jìn)行每幀都要渲染的邏輯時变骡,比如在IOS中frame發(fā)生改變時才進(jìn)行重新渲染時离赫,向runLoop發(fā)送一個消息源,同時向swapeBuffer監(jiān)聽的端口發(fā)送sync同步信號塌碌,當(dāng)runloop處理這個消息源時會調(diào)用渲染函數(shù)重新渲染渊胸,如果渲染邏輯過于復(fù)雜則不能很快的完成渲染,使得很長時間才到達(dá)緩存交換階段台妆,這樣會造成卡頓翎猛。

本文參考文章:

官方文檔

關(guān)于Runloop的原理探究及基本使用

Mach消息發(fā)送機(jī)制

opengl SwapBuffers的等待胖翰,虛偽的FPS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市切厘,隨后出現(xiàn)的幾起案子萨咳,更是在濱河造成了極大的恐慌,老刑警劉巖迂卢,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件某弦,死亡現(xiàn)場離奇詭異,居然都是意外死亡而克,警方通過查閱死者的電腦和手機(jī)靶壮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來员萍,“玉大人腾降,你說我怎么就攤上這事∷橐铮” “怎么了螃壤?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筋帖。 經(jīng)常有香客問我奸晴,道長,這世上最難降的妖魔是什么日麸? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任寄啼,我火速辦了婚禮,結(jié)果婚禮上代箭,老公的妹妹穿的比我還像新娘墩划。我一直安慰自己,他們只是感情好嗡综,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布乙帮。 她就那樣靜靜地躺著,像睡著了一般极景。 火紅的嫁衣襯著肌膚如雪察净。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天盼樟,我揣著相機(jī)與錄音塞绿,去河邊找鬼。 笑死恤批,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的裹赴。 我是一名探鬼主播喜庞,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼诀浪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了延都?” 一聲冷哼從身側(cè)響起雷猪,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晰房,沒想到半個月后求摇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殊者,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年与境,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猖吴。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡摔刁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出海蔽,到底是詐尸還是另有隱情共屈,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布党窜,位于F島的核電站拗引,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏幌衣。R本人自食惡果不足惜矾削,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泼掠。 院中可真熱鬧怔软,春花似錦、人聲如沸择镇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腻豌。三九已至家坎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吝梅,已是汗流浹背虱疏。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苏携,地道東北人做瞪。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親装蓬。 傳聞我的和親對象是個殘疾皇子著拭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1 Runloop機(jī)制原理 深入理解RunLoop http://www.cocoachina.com/ios/2...
    Kevin_Junbaozi閱讀 3,983評論 4 30
  • RunLoop 的概念 一般來講,一個線程一次只能執(zhí)行一個任務(wù)牍帚,執(zhí)行完成后線程就會退出儡遮。如果我們需要一個機(jī)制,讓線...
    Mirsiter_魏閱讀 614評論 0 2
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,435評論 0 13
  • 前言 RunLoop是iOS和OSX開發(fā)中非嘲蹈希基礎(chǔ)的一個概念鄙币,這篇文章將從CFRunLoop的源碼入手,介紹Run...
    暮年古稀ZC閱讀 2,229評論 1 19
  • 西方經(jīng)濟(jì)學(xué)蹂随、貨幣銀行學(xué)十嘿、金融市場學(xué)、國際經(jīng)濟(jì)學(xué)糙及、國際金融學(xué)详幽、商業(yè)銀行學(xué)、證券投資學(xué)浸锨、經(jīng)濟(jì)法唇聘、計量經(jīng)濟(jì)學(xué)、財政學(xué)柱搜、金...
    010ed0d5a362閱讀 163評論 0 0