在 LLDB 中 打印 bt 就可以打印函数调用栈
简介
RunLoop:运行循环
概念: 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出
应用范畴:
定时器(Timer)、PerformSelector
GCD Async Main Queue
事件响应、手势识别、界面刷新
网络请求
AutoreleasePool
如果没有运行循环的情况下
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
// 在执行了第5行代码后,程序会自动退出
如果有了运行循环
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
// 程序并不会马上退出,而是保持运行状态
// 伪代码
int main(int argc, char * argv[]) {
@autoreleasepool {
int retVal = 0;
do {
// 睡眠中等待消息
int message = sleep_and_wait();
// 处理消息
retVal = process_message(message);
} while (0 == retVal) ;
return 0;
}
}
RunLoop的基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件等)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
......
Runloop对象
iOS 中有两套API来访问和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象NSRunLoop是基于CFRunLoopRef的一层OC包装CFRunLoopRef是开源的
RunLoop与线程
每条线程都有唯一的一个与之对应的RunLoop对象RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value- 线程刚创建时并没有
RunLoop对象,RunLoop会在第一次获取它时创建RunLoop会在线程结束时销毁- 主线程的
RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
获取RunLoop对象
Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
打印
NSLog(@"%p %p", [NSRunLoop mainRunLoop], [NSRunLoop currentRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());
NSLog(@"%@",[NSRunLoop mainRunLoop]);
// 0x60000069aa60 0x60000069aa60
// 0x600001e98d00 0x600001e98d00
// <CFRunLoop 0x600001e98d00 [0x10eeefb68]>
通过打印结果可以看出,NSRunLoop对象是基于CFRunLoop的一层封装
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
/// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
/// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
CFDictionaryGetValue通过字典获取数据 上面的代码就等价于
loop = __CFRunLoops["pthreadPointer(t)"]
从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
RunLoop底层结构
-
CFRunLoopRef中的集合_modes存放着CFRunLoopModelRef对象 -
CFRunLoopModelRef又包含着三个集合-
_source0、_source1(存放着
CFRunLoopSourceRef) -
_observers (存放着
CFRunLoopObserverRef) -
_timers (存放着
CFRunLoopTimerRef)
-
Mode(CFRunLoopModeRef)
-
CFRunLoopModeRef代表RunLoop的运行模式 -
一个
RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer -
RunLoop启动时只能选择其中一个Mode,作为currentMode -
如果需要切换
Mode,只能退出当前Loop(从新进入,loop并没有结束),再重新选择一个Mode进入- 不同组的
Source0/Source1/Timer/Observer能分隔开来,互不影响
- 不同组的
-
如果
Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
source0
- 处理
触摸事件 (LLDB中bt可以打印出完整的函数调用栈)
可以顺便看一次啊点击手机屏幕的响应步骤
source1 捕捉事件,放到eventQueue , source0处理事件
3. CoreFoundation : __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
4. UIKitCore : -[UIApplication sendEvent:]
5. UIKitCore : -[UIWindow sendEvent:]
6. UIKitCore : - [UIWindow _sendTouchesForEvent:]
- 处理
performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:事件
source1
- 基于
port的线程间通信 - 系统事件的捕捉(触摸事件是
source1捕捉,包装成source0处理)
Timer
NSTimerperformSelector:withObject:afterDelay:
Observers
- 用于监听
runLoop状态 - UI刷新(
beforeWaiting) - Autorelease Pool
CFRunLoopModeRef
常见的2种Mode
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
CFRunLoopObserverRef (runLoop的六种状态)
添加Observer监听RunLoop的所有状态
- (void)viewDidLoad {
[super viewDidLoad];
// kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
/*
* 可以看出处理点击事件的时候,会进入source
2021-03-31 15:24:14.237892+0800 01. ObserverRef[68299:1347218] kCFRunLoopBeforeSources
2021-03-31 15:24:14.239497+0800 01. ObserverRef[68299:1347218] 123
**/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"123");
}
监听Mode的转变
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
2021-03-31 15:38:57.780986+0800 01. ObserverRef[68562:1363675] kCFRunLoopExit - kCFRunLoopDefaultMode
2021-03-31 15:38:57.781314+0800 01. ObserverRef[68562:1363675] kCFRunLoopEntry - UITrackingRunLoopMode
2021-03-31 15:39:04.437094+0800 01. ObserverRef[68562:1363675] kCFRunLoopExit - UITrackingRunLoopMode
2021-03-31 15:39:04.437248+0800 01. ObserverRef[68562:1363675] kCFRunLoopEntry - kCFRunLoopDefaultMode
RunLoop的运行逻辑
Mode类型 | |||
|---|---|---|---|
Source0 | 触摸事件处理 | performSelector:onThread: | |
Source1 | 基于Port的线程间通信 | 系统事件捕捉 | |
Timers | NSTimer | performSelector:withObject:afterDelay: | |
Observers | 用于监听RunLoop的状态 | UI刷新(BeforeWaiting) | Autorelease pool(BeforeWaiting) |
源码分析:
**************** 第一步 *******************
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知observers 进入RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知observers 退出RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
**************** 第二步 *******************
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 通知observer,即将处理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知observer,即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理Source0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle) {
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 判断有没有Source1
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
// 如果有Source1,就跳转到handle_msg
goto handle_msg;
}
// 通知observers:即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// 等待别的消息来唤醒当前线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
__CFRunLoopUnsetSleeping(rl);
// 通知observers:如果有事件,runloop被唤醒,结束休眠,根据唤醒条件,处理事件
__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 { // 被source1唤醒
// 处理source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoo
}
// 处理blocks
__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;
}
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
************************ 第三步 ******************************
// 都会调用到类似的,很长的函数来处理问题
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
注意: GCD回到主线程的代码是依赖runLoop的,其他的GCD逻辑,并不依赖runLoop
// 依赖代码
disptach_async(dispatch_get_main_queue(), ^ {
NSLog(@"这里是依赖runLoop的,可以用bt打印堆栈信息查看");
});
RunLoop休眠的实现原理
只有内核层面的API才可以实现真正的休眠
RunLoop在实际开中的应用
控制线程生命周期(线程保活)
解决NSTimer在滑动时停止工作的问题
监控应用卡顿
性能优化
补充:
GCD很多东西是不依赖RunLoop的,GCD执行流程是与RunLoop是分开的,但是GCD有一种情况是交给RunLoop处理就是线程间通信
NSTimer失效
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
RunLoop的应用
处理NSTimer和UIScrollView的冲突
线程保活
warning:一般initWithTarget:容易造成循环引用
NSRunLoop的run方法,用于开启无限循环的线程
使用懒加载的时候一定要注意的情况:当_thread是懒加载时
if (!_thread) return;
如果写成
if (self.thread) return;
会造成循环引用