iOS底层探索 --- RunLoop(概念)

无论是在日常的开发,还是在面试的过程中,RunLoop是我们绕不开的话题。那么今天我们就来一起探讨一下RunLoop。


1、RunLoop是什么?

RunLoop是事件接收和分发机制的一种实现,是线程相关的基础框架的一部分,一个RunLoop就是一个事件处理的循环,用来调度工作以及处理输入事件。

RunLoop的本质是一个do-while循环,没事就休息,来活就干活。
与普通的while循环不同的是:
1、普通的while循环会导致CPU进入,一直消耗CPU
2、RunLoopwhile循环是一种,也就是数RunLoop具有

void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

2、RunLoop和线程的关系

一般在日常开发中,对于RunLoop的获取,主要有以下两种方式:

/// 源码
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    //pthread_main_thread_np 主线程
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

*******************
///使用
/*主线程的循环*/
CFRunLoopRef mainRunLoop = CFRunLoopMain();
/*当前线程的循环*/
CFRunLoopRef CurrentRunLoop = CFRunLoopGetCurrent();
  • 上面我们看到CFRunLoopGetMainCFRunLoopGetCurrent 中的返回值是通过_CFRunLoopGet0函数获取的,下面我们来看一下该函数源码:
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
    /// t 是传入的值
    /// kNilPthreadT 是静态常量 ‘static _CFThreadRef const kNilPthreadT = (_CFThreadRef)0;’
    /// 如果 t 不存在,则标记为 ‘主线程’。也即是说默认值是 ‘主线程’
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    ///‘__CFRunLoops’ : ‘static CFMutableDictionaryRef __CFRunLoops = NULL;’
    ///‘CFMutableDictionaryRef’ : ‘typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableDictionary) __CFDictionary * CFMutableDictionaryRef;’
    /// 所以 ‘__CFRunLoops’ 是一个可变字典
    if (!__CFRunLoops) {
        /// 创建全局字典,标记为 kCFAllocatorSystemDefault
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        /// 通过主线程,创建主运行循环
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        /// 利用 dict,进行 key-value 绑定操作,即可说明,线程和RunLoop是一一对应的
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
    }
    
    ///通过其他线程获取RunLoop
    CFRunLoopRef newLoop = NULL;
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        ///如果没有获取到,则新建一个RunLoop
        newLoop = __CFRunLoopCreate(t);
        ///将新建的RunLoop 与 线程进行 key-value 绑定
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    __CFUnlock(&loopsLock);
    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    if (newLoop) { CFRelease(newLoop); }
    
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
#else
            _CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
        }
    }
    return loop;
}

_CFRunLoopGet0中,我们可以看到,RunLoop有两种,一种是主线程的,一种是其它线程的;并且RunLoop和线程是的。

3、RunLoop的创建

  • _CFRunLoopGet0 中我们会发现,RunLoop 是通过 __CFRunLoopCreate创建的,我们来看一下 __CFRunLoopCreate的源码:
static CFRunLoopRef __CFRunLoopCreate(_CFThreadRef t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    /// 如果loop为空,直接返回NULL
    if (NULL == loop) {
        return NULL;
    }
    
    /// RunLoop属性配置
    (void)__CFRunLoopPushPerRunData(loop);
    _CFRecursiveMutexCreate(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate((uintptr_t)loop);
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
    loop->_fromTSD = 0;
    loop->_timerTSRLock = CFLockInit;
#if TARGET_OS_WIN32
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}
  • 进入CFRunLoopRef的定义,可以看到RunLoop是一个对象,是__CFRunLoop结构体的。
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
************************
struct __CFRunLoop {
    CFRuntimeBase _base;
    _CFRecursiveMutex _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
    _CFThreadRef _pthread; ///与线程一一对应
    uint32_t _winthread;
    CFMutableSetRef _commonModes; ///存储着 NSString 对象的集合(Mode的名称)
    CFMutableSetRef _commonModeItems; ///存储着被标记的通用模式 `source0` / `source1` / `Timer` / `Observer`
    CFRunLoopModeRef _currentMode; ///当前的运行模式
    CFMutableSetRef _modes; /// `RunLoop` 所有的 `Mode`(`CFRunLoopModeRef`)
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
    _Atomic(uint8_t) _fromTSD;
    CFLock_t _timerTSRLock;
};

__CFRunLoop的定义中,我们可以看到一个RunLoop依赖多个Mode,意味着一个RunLoop需要处理多个事务。我们再来看一下CFRunLoopModeRef

  • CFRunLoopModeRef
    • CFRunLoopModeRef 代表 RunLoop的运行模式;
    • 一个 RunLoop 包含若干个Model,每个Mode 又包含若干个Source0 / Source1 / Timer / Observer
    • RunLoop启动时只能选择其中一个Mode,作为currentMode
    • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入,切换模式不会导致程序退出;
    • 不同的Mode中的 Source0 / Source1 / Timer / Observer 能分割开来,互不影响;
    • 如果Mode里面没有任何 Source0 / Source1 / Timer / Observer,则RunLoop立马退出。
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    _CFRecursiveMutex _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
    __CFPort _timerPort;
    Boolean _mkTimerArmed;
#endif
#if TARGET_OS_WIN32
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

RunLoop的模式

ModeName 描述
NSDefaultRunLoopMode/KCFRunLoopDefaultMode 默认模式
UITrackingRunLoopMode 跟踪用户交互事件,用于ScrollView追踪触摸滑动,保证界面滑动的时候,不受其他Mode影响
NSRunLoopCommonModes/KCFRunLoopCommonModes 伪模式(默认包含KCFRunLoopDefaultMode&UITrackingRunLoopMode), 该模式不是实际存在的一种模式,它指示一个特殊的标记。是同步Source0/Source1/Timer/Observer到多个Mode中的技术方案。被标记为通用模式的Source0/Source1/Timer/Observer都会存放到_commonModeItems集合中,会同步这些Source0/Source1/Timer/Observer到多个Mode中。
UIInitializationRunLoopMod 在刚启动 App 时第进入的第一个 Mode ,启动完 成后就不再使用
GSEventReceiveRunLoopMode 接受系统内部事件,通常用不到

CFRunLoopModeRef 这样设计的好处是什么?RunLoop为什么会有多个Mode?

  • Mode 做到了屏蔽的效果,当RunLoop运行在Mode1下面的时候,是处理不了Mode2的事件的;
  • 比如NSDefaultRunLoopMode默认模式和UITrackingRunLoopMode滚动模式,滚动屏幕的时候,就会切换到滚动模式,就不用去处理默认模式下的事件了,保证了UITableView等的滚动流畅。
  • CFRunLoopSourceRef
    • RunLoop中有两个很重要的概念,一个是上面提到的模式,还有一个就是事件源
      事件源分为输入源(Input Sources)定时器源(Timer Sources)两种;
    • 输入源(Input Sources)中的共用体union中的version0version1分别对应Source0Source1
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
******************
struct __CFRunLoopSource {
    CFRuntimeBase _base;
    _CFRecursiveMutex _lock;
    CFIndex _order;         /* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
    } _context;
    _Atomic(Boolean) _signaled;
};
  • Source0Source1的区别
Input Source 区别
Source0 表示,即用户自定义事件。需要手动唤醒线程:添加Source0RunLoop并不会主动唤醒线程,需要手动唤醒;① 触摸事件处理,② performSelector:onThread:
Source1 表示,主要负责底层的通讯,具备唤醒能力。① 基于 port 的线程通讯,② 系统事件捕捉:系统事件捕捉是由Source1来处理,然后再交给Source0处理。
  • CFRunLoopTimerRef
    • CFRunLoopTimerNSTimer 是可以相互转换的;
    • performSelector:withObject:afterDelay: 方法会创建timer并添加到RunLoop中。
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
*****************
struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    _CFRecursiveMutex _lock;
    CFRunLoopRef _runLoop;              /// 添加该 timer 的 RunLoop
    CFMutableSetRef _rlModes;           /// 所有包含该 timer 的 modeName
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;           /* immutable 理想时间间隔*/
    CFTimeInterval _tolerance;          /* mutable 时间偏差*/
    uint64_t _fireTSR;                  /* TSR units */
    CFIndex _order;                     /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable 回调入口*/
    CFRunLoopTimerContext _context;    /* immutable, except invalidation */
};
  • CFRunLoopObserverRef
struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    _CFRecursiveMutex _lock;
    CFRunLoopRef _runLoop;              /// 添加 observer 的 RunLoop
    CFIndex _rlCount;
    CFOptionFlags _activities;          /* immutable 监听的活动状态*/
    CFIndex _order;                     /* immutable */
    CFRunLoopObserverCallBack _callout; /* immutable 回调入口*/
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};
  • CFRunLoopObserverRef用来监听RunLoop的6种活动状态
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),           /// 即将进入 RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),    /// 即将处理 Timers
    kCFRunLoopBeforeSources = (1UL << 2),   /// 即将处理Sources
    kCFRunLoopBeforeWaiting = (1UL << 5),   /// 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),    /// 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),            /// 即将推出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU   /// 表示以上所有状态
};

CFRunLoopObserverRef 中的_activities用来保存RunLoop的活动状态。当RunLoop的状态发生改变的时候,通过回调_callout通知所有监听这个状态的Observer

  • RunLoop执行流程图

参考资料:
https://www.jianshu.com/p/cc9d286a1a27
https://cloud.tencent.com/developer/article/1615141

你可能感兴趣的:(iOS底层探索 --- RunLoop(概念))