RunLoop的基本概念
- RunLoop运行循环,其本质是一个 do-while循环,与普通的while循环是有区别的,普通的while循环会导致CPU进入
忙等待状态,即一直消耗CPU
,而RunLoop则不会,RunLoop是一种闲等待,无事件处理时会进入休眠状态,不会消耗CPU资源
;
RunLoop的基本作用
- 保证应用程序能持续稳定的运行而不退出,例如我们的在app启动之后,默认会开启一个主运行循环RunLoop,其能让app持续稳定运行,不会中途退出,除非用户手动杀掉app进程;
- 处理App中的各种事件(触摸、定时器、performSelector),,也就是说RunLoop是一个事件处理循环;
- 节省CPU资源,提升应用程序的性能,即当没有事件处理时会进入休眠状态,不会消耗CPU资源;
RunLoop的源码分析
- OSX/iOS 系统中,提供了两个这样的对象:
NSRunLoop
和CFRunLoopRef
CFRunLoopRef
是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的,NSRunLoop
是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的; - CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看
- RunLoop不能手动创建,只能获取,被动的创建,下面提供了获取主线程RunLoop与获取当前正在运行RunLoop函数的源码:
//获取主线程RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
//获取当前正在运行的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
- 最终都会进入
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)
函数,参数是当前线程;
//全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//pthread_t为空 默认设置成main线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
//保证线程安全
__CFLock(&loopsLock);
if (!__CFRunLoops) {
//第一次进入时,初始化全局Dic,并先为主线程创建一个RunLoop。
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//创建主运行循环
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//将线程和RunLoop 以键值对的形式 存储在全局字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//获取其他线程的RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
//RunLoop不存在,创建一个新的RunLoop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//线程与RunLoop 键值对存在在全局字典中
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
- 易知
线程与RunLoop是以键值对
的形式存储在一个全局字典(__CFRunLoops)
中的,所以线程与RunLoop之间是一一对应的关系; - 当
全局字典(__CFRunLoops)
为空,即第一次初始化时,会创建主线程循环
,并将主线程和主RunLoop 以键值对的形式 存储在__CFRunLoops中; - 接下来,进入获取其他线程的RunLoop,若RunLoop不存在,会创建RunLoop,执行
__CFRunLoopCreate(t)
函数;
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
CFRunLoopRef loop = NULL;
CFRunLoopModeRef rlm;
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
if (NULL == loop) {
return NULL;
}
(void)__CFRunLoopPushPerRunData(loop);
__CFRunLoopLockInit(&loop->_lock);
loop->_wakeUpPort = __CFPortAllocate();
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
__CFRunLoopSetIgnoreWakeUps(loop);
//RunLoop的属性设置
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;
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
loop->_winthread = 0;
#endif
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}
- 创建RunLoop对象,并进行相关属性的设置;
RunLoop的底层结构
- RunLoop的结构体定义如下:
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;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
-
_currentMode
:表示RunLoop当前的模式,是CFRunLoopModeRef
结构体类型; -
_modes
:表示RunLoop的模式集合,说明RunLoop可以有多种不同的模式; -
_commonModeItems
:是组的Items集合;
RunLoop的模式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; //事件源0
CFMutableSetRef _sources1; //事件源1
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 */
};
- 可以看到__CFRunLoopMode中包含以下主要部分:
- 事件源:
_sources0
,_sources1
- 观察者源:
_observers
- 定时器源:
_timers
- RunLoop拥有多种不同的Mode,每个Mode中包含Source/Timer/Observer; Source/Timer/Observer又称为一组Item,
子线程的RunLoop中必须要有Item的存在,也就是说必须要有源,有任务事件处理,否则RunLoop会无事可做,就会自动退出
;
RunLoop_Mode.png
- RunLoopMode的类型有如下:
- RunLoopMode在苹果文档中提及的有五个,而在iOS中公开暴露出来的只有
NSDefaultRunLoopMode
和NSRunLoopCommonModes
,NSRunLoopCommonModes 实际上是一个Mode 的集合
,默认包括NSDefaultRunLoopMode
和NSEventTrackingRunLoopMode
; -
NSDefaultRunLoopMode
:App的默认 Mode,通常主线程是在这个 Mode 下运行的; -
NSConnectionReplyMode
; -
NSModalPanelRunLoopMode
; -
NSEventTrackingRunLoopMode
:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响; -
NSRunLoopCommonModes
:伪模式,灵活性更好;
- RunLoopMode在苹果文档中提及的有五个,而在iOS中公开暴露出来的只有
RunLoop的事件源_sources0与_sources1 -- CFRunLoopSourceRef
-
_sources0
,_sources1
属于CFRunLoopSourceRef
类型,其底层结构体如下所示:
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;
};
- CFRunLoopSourceRef 是事件产生的地方,可以看到Source源有两种类型:
Source0
和Source1
-
Source0 属于CFRunLoopSourceContext类型
:其只包含了一个回调(函数指针),它并不能主动触发事件,使用时你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件,主要包括触摸事件
,PerformSelectors事件
; - 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;
- 代码验证如下:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"点击");
}
@end
- 点击屏幕,控制台输入bt,打印函数调用堆栈如下:
Snip20210528_45.png
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
}
- (void)test{
NSLog(@"test");
}
@end
- 控制台输入bt,打印函数调用堆栈如下:
Snip20210528_47.png
-
Source1 属于CFRunLoopSourceContext1 类型
:其包含了一个 mach_port 和一个回调(函数指针),被用于基于Port的线程间通信
; - CFRunLoopSourceContext1结构体如下:
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);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
RunLoop的定时器源timers -- CFRunLoopTimerRef
- RunLoop的定时器timers的类型为
CFRunLoopTimerRef
,其底层结构体如下所示:
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
其是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用,其包含一个
时间长度
和一个回调(函数指针)
,当其加入到 RunLoop 时,RunLoop会注册对应的时间点
,当到指定时间点时,RunLoop会被唤醒以执行那个回调(函数指针);代码测试:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"NSTimer ---- timer调用了");
}];
}
@end
- 控制台输入bt,打印函数调用堆栈如下:
Snip20210528_48.png
RunLoop的观察者源Observers -- CFRunLoopObserverRef
- RunLoop的观察者Observers属于
CFRunLoopObserverRef类型
,其底层结构体如下:
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
- 每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接收到这个变化,可以观测的时间点状态有以下几个:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1),//即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2),//即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
- 测试代码:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//创建监听者
/*
第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
第三个参数 Boolean repeats:YES:持续监听 NO:不持续
第四个参数 CFIndex order:优先级,一般填0即可
第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要处理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要处理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒来了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 给RunLoop添加监听者
/*
第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
第二个参数 CFRunLoopObserverRef observer 监听者
第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的内存管理(Core Foundation)
凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了
*/
CFRelease(observer);
}
@end
- 点击屏幕,控制台打印结果如下:
Snip20210528_51.png
- 可以看到Observer确实能监听到RunLoop的运行状态,包括唤醒,休息,以及处理各种事件;
Items源所触发的事件回调类型
- 上面已经验证了Source0,定时器Timer与observer源的事件回调;
Snip20210528_49.png
- block调用:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
- 调用timer:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
- 响应source0:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- 响应source1:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
- GCD主队列:
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
- observer源:
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION_
_
RunLoop的运行
- 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);
}
- 内部调用
CFRunLoopRunSpecific
函数:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
////首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
////通知 Observers: RunLoop 即将进入loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
////进入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
////通知 Observers: RunLoop 即将退出loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
-
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
:通知Observers,RunLoop即将进入循环,此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush()
-
__CFRunLoopRun
:核心函数,进入RunLoop运行循环; -
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit)
:通知Observers,RunLoop即将退出循环,此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
- 进入__CFRunLoopRun源码,由于这部分代码较多,于是这里用伪代码代替。其主要逻辑是根据不同的事件源进行不同的处理,当RunLoop休眠时,可以通过相应的事件唤醒RunLoop;
//核心函数
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
//通过GCD开启一个定时器,然后开始跑圈
dispatch_source_t timeout_timer = NULL;
...
dispatch_resume(timeout_timer);
int32_t retVal = 0;
//处理事务,即处理items
do {
// 通知 Observers: 即将处理timer事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 Observers: 即将处理Source事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 处理sources0返回为YES
if (sourceHandledThisLoop) {
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 判断有无端口消息(Source1)
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
// 处理消息
goto handle_msg;
}
// 通知 Observers: 即将进入休眠 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// 等待被唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 通知 Observers: 被唤醒,结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被timer唤醒) {
// 处理Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}else if (被GCD唤醒){
// 处理gcd
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}else if (被source1唤醒){
// 被Source1唤醒,处理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 处理block
__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;//结束
}
}while (0 == retVal);
return retVal;
}
- 可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个
do-while 循环
,当调用 CFRunLoopRun()函数时,线程就会一直停留在这个循环里,直到超时或被手动停止,该函数才会返回;
image.png
RunLoop与AutoreleasePool之间的关系
- App启动后,苹果在主线程 RunLoop 里注册了
两个Observer
,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
; - 第一个Observer源
监听RunLoop的进入状态
,其回调会调用_objc_autoreleasePoolPush() 创建自动释放池
,此回调用执行的优先级是最高的,保证自动释放池的创建 在其他所有回调执行之前; - 第二个Observer源监听
RunLoop进入休眠状态
和RunLoop的退出状态
,RunLoop进入休眠状态时 回调执行 释放旧的自动释放池并创建新的自动释放池,RunLoop的退出状态时 回调执行释放自动释放池,此时回调执行的优先级是最低的,保证自动释放池的释放,在其他所有回调执行之后; - 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的,这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了;
- 调试,在工程中加入符号断点
_wrapRunLoopWithAutoreleasePoolHandler
,然后运行工程,我运行的Xcode版本为11.3.1;
Snip20210528_55.png
Snip20210528_56.png
面试题一:ScrollView发生滚动的时候,NSTimer为什么会停止工作?
- 当ScrollView发生滚动的时候,主线程的RunLoop会切换到UITrackingRunLoopModel模式下,而NSTimer默认是添加到UIDefaultRunLoopModel下的,所以NSTimer不会执行回调,当ScrollView停止滚动是主线程的RunLoop又会切换到UIDefaultRunLoopModel模式下,此时NSTimer会恢复工作,执行自己的回调方法,为了保证NSTimer在UITrackingRunLoopModel模式下也能工作,可将主线程RunLoop的model模式设置成组合模式NSRunLoopCommonModes;
面试题二:RunLoop的实际应用
-
应用一:线程保活
,一般情况下,当线程执行完任务之后就会自动退出销毁,如下所示:
#import "ViewController.h"
#import "YYThread.h"
@interface ViewController ()
@property(nonatomic,strong)YYThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[YYThread alloc]initWithTarget:self selector:@selector(callBack) object:nil];
[self.thread start];
}
- (void)callBack {
NSLog(@"%s %@ -- 开始",__func__,[NSThread currentThread]);
NSLog(@"%s -- 结束",__func__);
}
@end
- 自定义线程YYThread在执行完callBack方法之后,就会退出销毁,下面使用RunLoop保活YYThread线程,并能执行其他任务,实现如下:
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[YYThread alloc]initWithTarget:self selector:@selector(callBack) object:nil];
[self.thread start];
}
- (void)callBack {
NSLog(@"%s %@ -- 开始",__func__,[NSThread currentThread]);
//实现线程保活
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s -- 结束",__func__);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//点击让子线程 继续执行其他任务
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test {
NSLog(@"%s %@ -- 开始",__func__,[NSThread currentThread]);
}
点击屏幕,让YYThread线程继续执行test函数,说明RunLoop确实能让YYThread线程存活,继续执行其他任务;
应用二:监听App的卡顿
,原理是子线程向主线程发送消息,若主线程未在指定的时间内作出应答,说明主线程在执行耗时任务,存在App的卡顿,代码实现见iOS性能优化01 -- 卡顿优化应用三:优化tableView的滚动
,利用RunLoop的Model特性,可将更新UI的操作放到
UIDefaultRunLoopModel模式中,这样tableView在滚动时不会因为更新UI而影响到平滑的滚动效果,网络图片加载完成,更新UI到ImageView时,使用UIDefaultRunLoopModel模式,代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *networkImage = nil;
[self.icon performSelector:@selector(setImage:) withObject:networkImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
}