一、RunLoop 的运行逻辑
1. Source0
、 Source1
、 Timers
、 Observers
的作用?
大概了解一下,有个印象就行
2. RunLoop 的运行逻辑
RunLoop 的运行逻辑图(了解即可)
3. 一句话概括上面的流程图?
- RunLoop 就是进入某一种循环,然后把
Source0
、Source1
、Timers
、Observers
拿出来执行以下,然后进入休眠,等待新的消息唤醒它
4. RunLoop 休眠的实现原理?它和代码 while(1);
这种死循环有什么区别?
- RunLoop 的休眠是从
用户态
到内核态
(Linux 的计算机知识) - RunLoop 的唤醒是从
内核态
到用户态
-
while(1);
是让线程一直执行;
代码,是让程序一直在跑,并没有进行休眠,很浪费资源。
二、RunLoop 和 NSTimer
1. 为什么默认情况下 NSTimer
,在用户拖拽滚动的时候会停止调用?
-
NSTimer
是由 RunLoop 在NSDefaultRunLoopMode
模式下调度执行的 - RunLoop 默认情况会在
NSDefaultRunLoopMode
模式下运行,所以一般情况下 NSTimer 能正常运行 - 当用户拖拽滚动的时候,RunLoop 会进入
UITrackingRunLoopMode
模式,所以 NSTimer 不会运行
2. 如何解决默认情况下 NSTimer
,在用户拖拽滚动的时候会停止调用?
- 将 NSTimer 设定成
NSRunLoopCommonModes
- 严格来讲
NSRunLoopCommonModes
并不是 RunLoop 的一种模式
RunLoop结构体
-
_commonModes
里面装着NSDefaultRunLoopMode
和UITrackingRunLoopMode
-
NSRunLoopCommonModes
就是_commonModes
里面模式都能得到执行
二、RunLoop 用于线程保活
1. 什么是线程保活?
- 默认情况下,一个线程执行完需要执行的代码就会
挂掉
-
线程保活
就是由程序猿自己控制线程的死活
2. 如何用NSThread
创建一个一般线程(创建→执行代码→执行完成销毁)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
[thread start];
}
- (void)threadStart {
NSLog(@"%s", __func__);
}
-
YYThread
继承自NSThread
主要重写-dealloc 方法
用于观察线程的释放
3. 如果我们希望上面的线程一直存活要怎么办?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
[thread start];
}
- (void)threadStart {
NSLog(@"%s", __func__);
while (1);
}
- 上述代码就可以实现线程一直不死,但是不是我们想要的,我们想要它有事做事,无事休眠
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
[thread start];
}
- (void)threadStart {
NSLog(@"---------start-------- %s", __func__);
// 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
[[NSRunLoop currentRunLoop] run];
NSLog(@"---------end-------- %s", __func__);
}
- 思考上述代码,可以让线程不死吗?
- 线程依然会死掉,虽然我们用获取方法创建了 RunLoop,但是前面学过
如果启动RunLoop时 Mode 里面没有任何 Source0/Source1/Timer/Observer,RunLoop 会立马退出
- 所以我们考虑往 RunLoop 中添加一个任务
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
[thread start];
}
- (void)threadStart {
NSLog(@"---------start-------- %s", __func__);
// 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"---------end-------- %s", __func__);
}
- 这样我们的 RunLoop 就算创建并且真正运行了。
- 思考我们能看到
NSLog(@"---------end-------- %s", __func__);
的打印信息吗? - 不能,因为 RunLoop 就是一个运行循环,它会卡住当前线程,让线程一直不死,所以在 RunLoop 挂掉之前,
NSLog(@"---------end-------- %s", __func__);
都不会被执行
5. 思考下面的代码,当控制器退出销毁时,线程会被销毁吗?
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[YYThread alloc] initWithBlock:^{
NSLog(@"---------start-------- %s", __func__);
// 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"---------end-------- %s", __func__);
}];;
[self.thread start];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
}
- (void)stop {
[self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:NO];
NSLog(@"%s", __func__);
}
- (void)stopRunLoop {
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self stop];
self.thread = nil;
}
@end
- 线程不会销毁
- 因为
[[NSRunLoop currentRunLoop] run];
这个函数,我们看下官方说明
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the
NSDefaultRunLoopMode
by repeatedly invokingrunMode:beforeDate:
. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
如果没有任何 sources 或者 timers 被添加到 RunLoop,那么这个 run 方法会离开退出;
它会在
NSDefaultRunLoopMode
模式下重复调用runMode:beforeDate:
方法换句话说:这个方法就会无限循环处理来自
sources
和timers
的数据CFRunLoopStop(CFRunLoopGetCurrent());
只能停掉一次runMode:beforeDate:
,然后[[NSRunLoop currentRunLoop] run];
会立刻再调用runMode:beforeDate:
所以线程无法被销毁
6. 思考下面的代码,touchesBegan:withEvent:
被调用时,线程会被销毁吗?
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.stop = NO;
__weak typeof(self) weakSelf = self;
self.thread = [[YYThread alloc] initWithBlock:^{
NSLog(@"---------start-------- %s", __func__);
// 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"---------end-------- %s", __func__);
}];;
[self.thread start];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// [self performSelector:@selector(doSoming) onThread:self.thread withObject:nil waitUntilDone:NO];
self.stop = YES;
[self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:YES];
self.thread = nil;
}
- (void)doSoming {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)stopRunLoop {
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
- 会,这就是一个完整的保活线程的例子
- 不过还存在一个问题,如果在
-[ViewController dealloc]
中如下调用
- (void)dealloc {
NSLog(@"%s", __func__);
self.stop = YES;
[self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:YES];
self.thread = nil;
}
- 当控制器销毁时,为什么 Thead 并没有被销毁呢?
- 因为
while (!weakSelf.isStop)
中的weakSelf
被释放了,所以while (!weakSelf.isStop)
这个循环即使设置了stop = YES
,仍然会进入循环,所以我们只需完善逻辑改成while (weakSelf && !weakSelf.isStop)
,那线程和控制器都能都在- (void)dealloc
中被释放掉。
三、RunLoop 线程保活 -- 代码封装
我们可以看上,如果上面的线程保活的功能,我们需要在多处调用,那么将会很麻烦,每个要用到的地方都需要添加很多相关代码。基于此,我们可以把它封装成一个
迷你工具库
。
1. 思考,如果我们把上面的线程保活功能封装成一个对象,这个对象应该继承自谁呢?继承自NSThread
? NSThread的分类
? 继承自NSObject
?
- 如果使用
NSThread的分类
,那么我们添加属性的时候
,需要用到关联对象添加
,非常麻烦 - 如果使用
NSThread的分类
和继承自NSThread
都存在一些问题:①使用者能拿到 thread 对象,调用其方法进行随意修改 ②使用者使用的时候,包含的方法太多,会让使用者迷茫如何正确使用 - 所以,我们最终的选择是让它
继承自NSObject
2. 封装代码
// 线程保活.h文件代码
#import
typedef void (^permanentThreadTask)(void);
@interface YYPermanentThread : NSObject
- (void)run;
- (void)stop;
- (void)executeTask:(permanentThreadTask)task;
@end
// 线程保活.m文件代码
#import "YYPermanentThread.h"
/* 这个对象主要用于监测 NSThread 的释放 */
@interface YYThread : NSThread
@end
@implementation YYThread
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
/* 我们封装的线程保活工具 */
@interface YYPermanentThread ()
@property(nonatomic, strong) NSThread *thread;
@property(nonatomic, assign, getter=isStopped) BOOL stopped;
@end
@implementation YYPermanentThread
- (instancetype)init
{
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
self.thread = [[YYThread alloc] initWithBlock:^{
NSLog(@"-------RunLoop start-------");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"-------RunLoop end-------");
}];
}
return self;
}
- (void)run {
if (self.thread == nil) return;
[self.thread start];
}
- (void)stop {
if (self.thread == nil) return;
[self performSelector:@selector(__stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)executeTask:(permanentThreadTask)task {
if (self.thread == nil || task == nil) return;
[self performSelector:@selector(__executeTask:) onThread:self.thread withObject:task waitUntilDone:NO];
}
#pragma mark - private method
- (void) __stopThread {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
}
- (void) __executeTask:(permanentThreadTask)task {
task();
}
- (void)dealloc {
if (self.thread != nil){
[self stop];
}
NSLog(@"%s", __func__);
}
@end
- 然后我们使用起来就非常容易了
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.permanentThread = [[YYPermanentThread alloc] init];
[self.permanentThread run];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.permanentThread executeTask:^{
NSLog(@"我在子线程干活---%@",[NSThread currentThread]);
}];;
}
@end
3. 我们也可以基于C语言的 RunLoop 来封装,把上面 - (instancetype)init
里面的代码,换成如下代码即可。
- (instancetype)init
{
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
self.thread = [[YYThread alloc] initWithBlock:^{
NSLog(@"-------RunLoop start-------");
CFRunLoopSourceContext context = {0};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
while (weakSelf && !weakSelf.isStopped) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000, NO);
}
NSLog(@"-------RunLoop end-------");
}];
}
return self;
}
四、线程卡顿检测
1、如何打印堆栈信息?
- 借助Linux的内核函数,backtrace() 和 backtrace_symbols()
#import
- (void)logStack {
void* callstack[256];
int frames = backtrace(callstack, 256);
char **strs = backtrace_symbols(callstack, frames);
_backtrace = [NSMutableArray arrayWithCapacity:frames];
for (int i = 0; i < frames; i++) {
[_backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
NSLog(@"====================堆栈\n %@ \n",_backtrace);
}
2、如何检测卡顿?
- 核心思想:①创建一个observer,加入到主线程的观察者队列中,用于监测主线程的runloop状态变更 ②创建一个子线程,为kCFRunLoopBeforeSources 、kCFRunLoopAfterWaiting两个计时,如果停留在某个状态过久,就表示卡顿了。
#import
@interface SeMonitorController : NSObject
+ (instancetype) sharedInstance;
- (void) startMonitor;
- (void) endMonitor;
- (void) printLogTrace;
@end
#import "SeMonitorController.h"
#import
#import
@interface SeMonitorController(){
CFRunLoopObserverRef _observer;
dispatch_semaphore_t _semaphore;
CFRunLoopActivity _activity;
NSInteger _countTime;
NSMutableArray *_backtrace;
}
@end
@implementation SeMonitorController
+ (instancetype) sharedInstance{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void) startMonitor{
[self registerObserver];
}
- (void) endMonitor{
if (!_observer) {
return;
}
CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
CFRelease(_observer);
_observer = NULL;
}
- (void) printLogTrace{
NSLog(@"====================堆栈\n %@ \n",_backtrace);
}
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
SeMonitorController *instrance = [SeMonitorController sharedInstance];
instrance->_activity = activity;
// 发送信号
dispatch_semaphore_t semaphore = instrance->_semaphore;
dispatch_semaphore_signal(semaphore);
}
- (void)registerObserver
{
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
// 创建信号
_semaphore = dispatch_semaphore_create(0);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (YES)
{
// 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
long st = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
if (st != 0)
{
if (_activity==kCFRunLoopBeforeSources || _activity==kCFRunLoopAfterWaiting)
{
if (++_countTime < 5)
continue;
[self logStack];
NSLog(@"something lag");
}
}
_countTime = 0;
}
});
}
- (void)logStack{
void* callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
int i;
_backtrace = [NSMutableArray arrayWithCapacity:frames];
for ( i = 0 ; i < frames ; i++ ){
[_backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
}
@end