无论是在日常的开发,还是在面试的过程中,RunLoop是我们绕不开的话题。那么今天我们就来一起探讨一下RunLoop。
1、RunLoop是什么?
RunLoop
是事件接收和分发机制的一种实现,是线程相关的基础框架的一部分,一个RunLoop
就是一个事件处理的循环,用来调度工作以及处理输入事件。
RunLoop
的本质是一个do-while
循环,没事就休息,来活就干活。
与普通的while
循环不同的是:
1、普通的while
循环会导致CPU
进入,一直消耗
CPU
。
2、RunLoop
的while
循环是一种,也就是数
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();
- 上面我们看到
CFRunLoopGetMain
和CFRunLoopGetCurrent
中的返回值是通过_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
中的version0
和version1
分别对应Source0
和Source1
- 在
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;
};
-
Source0
和Source1
的区别
Input Source | 区别 |
---|---|
Source0 |
表示Source0 到RunLoop 并不会主动唤醒线程,需要手动唤醒;① 触摸事件处理,② performSelector:onThread: 。 |
Source1 |
表示port 的线程通讯,② 系统事件捕捉:系统事件捕捉是由Source1 来处理,然后再交给Source0 处理。 |
-
CFRunLoopTimerRef
-
CFRunLoopTimer
和NSTimer
是可以相互转换的; -
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
网友评论