runloop 的状态待完善
相关面试题
- 讲讲 RunLoop,项目中有用到吗?
1、控制线程的生命周期(线程保活)
2、解决 NSTimer 在滑动时停止工作的问题
3、监控应用卡帧
4 、性能优化 - runloop内部实现逻辑?
- runloop和线程的关系?
线程与 runloop 一一对应,主线程的 runloop 默认开启,子线程的 runloop 在第一次获取的时候开启,runloop 在线程结束时销毁,线程和 runloop 存放在一个全局的字典中,该字典以线程 key,runloop 作为 value. - timer 与 runloop 的关系?
timer 需要添加到 runloop 中才能工作,runloop 中有一个CFRunLoopTimerRef 类型的事件源专门用来出来 timer 事件. - 程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
将 timer 添加到 runloop 的 commonModel 模式下 - runloop 是怎么响应用户操作的, 具体流程是什么样的?
- 说说runLoop的几种状态
- runloop的mode作用是什么?
什么是 RunLoop
- RunLoop 顾名思义就是在运行过程中循环做一些事情
- 应用范畴:
1、定时器(Timer)、PerformSelector
2、GCD Async Main Queue
3、事件响应、手势识别、界面刷新
4、网络请求
5、AutoreleasePool
RunLoop的作用
1、保持程序的持续运行
2、处理App中的各种事件(比如触摸事件、定时器事件等)
3、节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- 如果没有 RunLoop 程序执行完就会退出,例如下面的代码,程序在执行完main 函数后就会退出。
int main (int argc, const char * argv[]){
@autoreleasepool{
NSLog(@"hello world");
}
return 0;
}
- 但是我们通常看到的ios 程序运行在手机上后就一直处在运行状态等待用户的响应,它是怎么做到的呢?其实是因为ios 应用程序在 UIApplicationMain函数中帮我们开启了一个 runloop,runloop 保持我们的 ios 程序持续运行,下面是一个 ios 的 main 函数:
int main(int argc, char *argv[]){
@autoreleasepool{
return UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class]));
}
}
- 那么 runLoop 是怎么实现的呢?
下面用伪代码来实现以下
int main(int argc, char *argc[]){
int retVal = 0;
do{
// 睡眠中等待消息
int message = sleep_and_wait();
// 处理消息,处理完消息返回 0,继续 do...while 循环
retVal = process_message(message);
} while(0 == retVal);
return 0;
}
RunLoop 对象
- iOS中有2套API来访问和使用RunLoop
1、Foundation:NSRunLoop
2、Core Foundation:CFRunLoopRef - NSRunLoop和CFRunLoopRef都代表着RunLoop对象
- NSRunLoop是基于CFRunLoopRef的一层OC包装
- CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/
RunLoop 与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
1、获取 Runloop的方式:
[NSRunLoop currentRunLoop];
获取当前线程的 RunLoop 对象
[NSRunLoop mainRunLoop];
获取主线程的 RunLoop 对象
或者
CFRunLoopGetCurrent();
获得当前线程的 RunLoop 对象
CFRunLoopGetMain()
获得主线程的 Runloop 对象 - 下面我们通过源码来验证上面的结论:
由于 CFRunLoop 是开源的我们可以在源码中找到CFRunLoopGetCurrent
函数
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
上面我们看到其返回的是一个_CFRunLoopGet0
函数
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 这里我们可以看到 loop 对象是从CFDictionaryGetValue这个字典中取出来的,而且是以线程指针作为 key,runloop 对象作为 value 的,
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 这里如果从字典中去到的loop 对象是空,那么就会创建一个新的线程对象
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 这里将新的 newLoop 对象赋值给 loop,并保存在字典中
if (!loop) {
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 相关的类
- Core Foundation中关于RunLoop的5个类
CFRunLoopRef :
-
CFRunLoopRef
其实是一个__CFRunLoopRef
类型的结构体,其有两个比较中要的变量CFRunLoopModeRef _currentMode; CFMutableSetRef _modes;
_currentMode
存放的是CFRunLoopModeRef
类型的当前mode
_modes
存放的是当前RunLoop
对象中所有的CFRunLoopModeRef
类型的mode集合
_commonModes
中存放的是所有标记为 commonModel 的集合,例如kCFRunLoopDefaultMode
、UITrackingRunLoopMode
_commonModeItems
中存放的是所有添加到kCFRunLoopCommonModes的所有对象,
源码如下:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
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;
};
其结构如下:
image.png
CFRunLoopModeRef
- 它其实是一个
__CFRunLoopMode
类型的结构体,其有四个比较重要的变量CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers;
_sources0
和_sources1
中存放的是CFRunLoopSourceRef
类型的变量;
_observers
中存放的是CFRunLoopObserverRef
类型的变量;
_timers
中存放的是CFRunLoopTimerRef
类型的变量;
源码如下:
typedef struct __CFRunLoopMode *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 */
};
- CFRunLoopModeRef 代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
,RunLoop启动时只能选择其中一个Mode,作为currentMode,如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入,不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响,如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。
目前已知的Mode有5种
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到.
- kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode.添加到该模式下的事件源可以在所有标记为 commonMode的mode(kCFRunLoopDefaultMode、UITrackingRunLoopMode) 下运行
CFRunLoopSourceRef
- _modes中的_sources0和_sources1的类型;
- _sources0主要处理,触摸时间和performSelector:onThread:类型的事件
下面我们用源码验证一下,首先验证触摸事件的
我们新建一个viewController中添加一个touchesBegan:方法,并在NSLog处打上断点,
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"touchesBegan");
}
点击屏幕触发断点,然后我们在控制台执行bt,得到函数调用栈信息如下:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000105516efd RunLoop`-[ViewController touchesBegan:withEvent:](self=0x00007f8b9e705a60, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600001884d80) at ViewController.m:25:5
frame #1: 0x00007fff48bf59c6 UIKitCore`forwardTouchMethod + 323
frame #2: 0x00007fff48bf5872 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x00007fff48c0478f UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
frame #4: 0x00007fff48c067f5 UIKitCore`-[UIWindow sendEvent:] + 4501
frame #5: 0x00007fff48be0c39 UIKitCore`-[UIApplication sendEvent:] + 356
frame #6: 0x00007fff48c6b096 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 7328
frame #7: 0x00007fff48c6e262 UIKitCore`__handleEventQueueInternal + 6565
frame #8: 0x00007fff48c64dcb UIKitCore`__handleHIDEventFetcherDrain + 88
frame #9: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #10: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
frame #11: 0x00007fff23d9d5b4 CoreFoundation`__CFRunLoopDoSources0 + 180
frame #12: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
frame #13: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #14: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #15: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #16: 0x00000001055171c2 RunLoop`main(argc=1, argv=0x00007ffeea6e7cb8) at main.m:18:12
frame #17: 0x00007fff519521fd libdyld.dylib`start + 1
frame #18: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb)
在frame #9中我们看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
可以验证该事件源来自source0。
下面我们验证performSelector:onThread:类型的,在viewDidLoad方法中加入如下代码,并在test方法内打上断点
dispatch_async(dispatch_get_global_queue(0,0), ^{
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:true];
});
-(void)test{
NSLog(@"test");
}
触发断点,在控制台执行bt,得到如下函数堆栈信息:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000102a14e27 RunLoop`-[ViewController test](self=0x00007fcaab50b2e0, _cmd="test") at ViewController.m:28:5
frame #1: 0x00007fff2593af42 Foundation`__NSThreadPerformPerform + 209
frame #2: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #3: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
frame #4: 0x00007fff23d9d60c CoreFoundation`__CFRunLoopDoSources0 + 268
frame #5: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #9: 0x0000000102a15152 RunLoop`main(argc=1, argv=0x00007ffeed1e9cb8) at main.m:18:12
frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb)
从frame #2中可以看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
验证是source0。
-
那么source1是干嘛的呢?source1主要是用来处理基于Port的线程间通信 和 系统事件捕捉的
-
source0 和 source1 有什么区别呢?
Source1 :基于mach_Port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的RunLoop(iOS里进程间通信开发过程中我们一般不主动使用)。mach_port大家就理解成进程间相互发送消息的一种机制就好, 比如屏幕点击, 网络数据的传输都会触发sourse1。Source0 :非基于Port的 处理事件,什么叫非基于Port的呢?就是说你这个消息不是其他进程或者内核直接发送给你的。一般是APP内部的事件, 比如hitTest:withEvent的处理, performSelectors的事件。简单举个例子:一个APP在前台静止着,此时,用户用手指点击了一下APP界面,那么过程就是下面这样的:
我们触摸屏幕,先摸到硬件(屏幕),屏幕表面的事件会被IOKit先包装成Event,通过mach_Port传给正在活跃的APP , Event先告诉source1(mach_port),source1唤醒RunLoop, 然后将事件Event分发给source0,然后由source0来处理。
如果没有事件,也没有timer,则runloop就会睡眠, 如果有,则runloop就会被唤醒,然后跑一圈。
我们可以这样理解source1是系统事件,source0是应用层事件,source1接收到事件后将事件分发给source0来处理
CFRunLoopTimerRef
- _modes中的_timers的类型,主要处理定时器事件
我们在viewDidLoad中添加下面源码,并在test方法内打上断点,
[NSTimer scheduledTimerWithTimeInterval:3 repeats:true block:^(NSTimer * _Nonnull timer) {
[self test];
}];
触发断点,在终端中执行bt,得到如下信息:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000102b44e27 RunLoop`-[ViewController test](self=0x00007fcc0bc0a490, _cmd="test") at ViewController.m:32:5
frame #1: 0x0000000102b44d7b RunLoop`__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x00006000018c02d0, timer=0x00006000023a8900) at ViewController.m:25:9
frame #2: 0x00007fff2594462e Foundation`__NSFireTimer + 72
frame #3: 0x00007fff23d9e634 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
frame #4: 0x00007fff23d9e2ce CoreFoundation`__CFRunLoopDoTimer + 1038
frame #5: 0x00007fff23d9d92a CoreFoundation`__CFRunLoopDoTimers + 282
frame #6: 0x00007fff23d9857e CoreFoundation`__CFRunLoopRun + 1950
frame #7: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #8: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #9: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #10: 0x0000000102b45152 RunLoop`main(argc=1, argv=0x00007ffeed0b9cb8) at main.m:18:12
frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
frame #12: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb)
在frame #3:行中我们看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
验证是处理定时器事件的。
CFRunLoopObserverRef
- 用来监听RunLoop的状态
- 对应状态如下:
RunLoop是怎样的一个循环
- 流程如下:
01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第8步
07、通知Observers:开始休眠(等待消息唤醒)
08、通知Observers:结束休眠(被某个消息唤醒)
01> 处理Timer
02> 处理GCD Async To Main Queue
03> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop
- 上面我们在执行断点时在终端使用bt查看函数调用栈的时候都会看到CoreFoundation __CFRunLoopRun这个函数,其实这个就是Runloop启动的入口。在源码CFRunLoop.c中可以找到CFRunLoopRun函数,源码如下:
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);
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
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 进行runloop 的核心逻辑
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出runloop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
通过源码看到返回的result是通过__CFRunLoopRun函数获取的,__CFRunLoopRun函数源码比较多,下面是提取出来的核心代码
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
// 先判断是否是stopped状态,如果是返回stopped的状态
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
int32_t retVal = 0;
do {
// 通知Observers 即将处理Timers
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers 即将处理Sources
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理Blocks,是NSRunLoop 中的 performBlock:函数中的block
__CFRunLoopDoBlocks(rl, rlm);
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {、
// 处理Blocks,是NSRunLoop 中的 performBlock:函数中的block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//如果有Sources1,就跳转打handle_msg标记处
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
}
// 通知Observers即将休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 进入休眠,等待其他消息唤醒
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
// 休眠后进去do...while(1)的死循环,循环里等待唤醒的消息,如果被唤醒执行break跳出死循环
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
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);
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.
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 {
break;
}
} while (1);
// 醒来
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
__CFRunLoopUnsetSleeping(rl);
// 通知Observers 已经被唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 被timer唤醒,处理Timer
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
else if (livePort == dispatchPort) { // 被GCD 唤醒,处理GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被sources1唤醒,处理Sources1
if (rls) {
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 执行Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 根据之前的执行结果,来决定怎么做,如果retVal被标记为非零,当前循环结束,返回retVal,CFRunLoopRunSpecific中通知observers退出runloop,如果是Stopped或Finished则外层CFRunLoopRun中的do...while循环结束,代码执行完毕,线程结束
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);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
- GCD 如果是异步回到主线程的话是由runloop处理的
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"111-----%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"222-----%@",[NSThread currentThread]);
});
});
在NSLog(@"222-----%@",[NSThread currentThread]);处断点,出发断点后,终端执行bt获得函数调用栈如下
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x000000010f21dd70 RunLoop`__29-[ViewController viewDidLoad]_block_invoke_2(.block_descriptor=0x000000010f2200a0) at ViewController.m:31:33
frame #1: 0x000000010f487f11 libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #2: 0x000000010f488e8e libdispatch.dylib`_dispatch_client_callout + 8
frame #3: 0x000000010f496d97 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1149
frame #4: 0x00007fff23d9da89 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
frame #5: 0x00007fff23d985d9 CoreFoundation`__CFRunLoopRun + 2041
frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #9: 0x000000010f21e102 RunLoop`main(argc=1, argv=0x00007ffee09e1cb8) at main.m:18:12
frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
通过函数调用栈看到其是通过CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE函数来处理的
runloop示意图.png线程保活
线程一旦执行完线程内的任务就会死亡,如果不想让线程在执行完任务之后死亡,就需要给线程的 runloop 的 mode 中添加source0、source1、timer、observer如果 runloop 中有四个中的任意一种就不会退出,runloop 不退出线程内的任务就没执行完,就不会销毁,我们可以给 runloop 添加一种事件源,让其不退出
@interface ZWThread : NSThread
@end
@implementation ZWThread
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
@interface ViewController ()
@property (strong, nonatomic) ZWThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[ZWThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer, 这样 runloop 不会立即退出
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStoped) {
// 这里运行 runloop,其实是"阻塞"线程,当 runloop 接收到事件时去处理事件,处理完了之后就休眠,当收到下次事件时重新判断 while 的条件是否成立,如果成立,继续执行循环体,如果不成立跳出 while 循环
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// 只有当 runloop stop 的时候才会走到这里打印,没有 stop 的话,就一直在 while 循环体内处理事件和休眠.
NSLog(@"%@----end----", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// if (!self.thread) return;
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test{
NSLog(@"after %s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
if (!self.thread) return;
// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 用于停止子线程的RunLoop
- (void)stopThread{
// 设置标记为YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 清空线程
self.thread = nil;
}
- (void)dealloc{
NSLog(@"%s", __func__);
[self stop];
}
网友评论