runloop底层实现(二)mode

本文重点讨论到底什么是runloopmode及业务逻辑。

 CFRunLoop 结构如下:
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock; /* locked for accessing mode list */
    __CFPort _wakeUpPort; // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

其中CFRunLoopModeRef _currentMode;是对runloopmode的定义。
通过currentMode这个名字即可以看出runloop的mode是可以改变的。
而CFMutableSetRef _commonModes;是整个runloop所包含的mode的集合,请注意这里的_commonModes并不是等价于
kCFRunLoopCommonModes。

先看CFRunLoopModeRef的结构:

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock; /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    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
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

其中:
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
分别对应着runloop中常常提到的三种source、observer、timer也就是数组,存放了这三种结构。
通过CFRunLoopAddSource、CFRunLoopAddObserver、CFRunLoopAddTimer分别添加。
代码如下:

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rls)) return;
    Boolean doVer0Callout = false;
    __CFRunLoopLock(rl);
//当添加的source注册到了commonmodes的时候
    if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
//将source增加到_commonModeItems中
    rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
CFSetAddValue(rl->_commonModeItems, rls);
if (NULL != set) {
    CFTypeRef context[2] = {rl, rls};
    /* add new item to all common-modes */
//将新增的source同步到_commonModes的所有mode中
    CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
    CFRelease(set);
}
    } else {
......
}
}

简单来说就是如果一个source注册到了kCFRunLoopCommonModes就相当于注册到了所有在_commonModes数组里的mode之中,_commonModes里默认有两种mode::kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。UITrackingRunLoopMode是scrollview滑动时所在的mode,因此如果在滑动scrollview需要保证图片下载,音频播放等source的性能,将这些source注册为kCFRunLoopCommonModes即可。

而source到底是什么呢,在代码中可以找到如下定义:

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order; /* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0; /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
    } _context;
};

CFRunLoopSourceContext定义如下:

typedef struct {
    CFIndex version;
    void * info;
    const void *(*retain)(const void *info);
    void (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode (*hash)(const void *info);
    void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void (*perform)(void *info);
} CFRunLoopSourceContext;

其中的void (*perform)(void *info);便是soure回调方法的地址。
runloop对source调用的过程如下:

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
......
 
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
......
            
         CFRUNLOOP_WAKEUP_FOR_SOURCE();
         // Despite the name, this works for windows handles as well
         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;          
......
}
 
 
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) { /* DOES CALLOUT */
......
                        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
......
}
 
 
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) {
    if (perform) {
        perform(info);
    }
    getpid(); // thwart tail-call optimization
}

你可能感兴趣的:(runloop底层实现(二)mode)