0.RunLoop是什么?
运行循环,苹果为保证程序持续运行而设计的一套机制。
1.Runloop的作用
- 保证程序持续运行(类似while循环,但runloop更加优化)
- 处理App中各种事件(触摸,定时器,performSelector,source源)
- 节省CPU资源,提高程序的性能(有事启动,无事休眠)
2.RunLoop与线程的关系
线程和runloop是一一对应的关系
- 底层会通过字典去存储线程和与之对应的runloop
- 线程与runloop通过key-value的方式进行一一对应。
- 子线程默认不开启runloop
- 一个RunLoop只能选择一种model进行启动
3.点击屏幕,然后滑动textView,timer出现阻塞的原因?
//在ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.myTextView = [[UITextView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width,80)];
[self.view addSubview:self.myTextView];
self.myTextView.text = @"Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet";
self.myTextView.delegate = self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self timerDemo01];
}
- (void)timerDemo01{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"fire in home -- %@ - %@",[[NSRunLoop currentRunLoop] currentMode],[NSThread currentThread]);
}];
}
#pragma mark - UITextViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"scrollViewDidScroll:%@ - %@",[[NSRunLoop currentRunLoop]currentMode],[NSThread currentThread]);
}
- 调用scheduledTimerWithTimeInterval,timer会被添加到当前RunLoop运行
- timer会在主线程运行,主RunLoop以NSDefaultRunLoopModel的模式运行
- 当滑动UITextView时,RunLoop响应滑动事件,主RunLoop的模式会以NSTrackingRunloopModel的模式启动
- 我们知道一个RunLoop只能选择一种model进行启动,所以timer会被阻塞。
4.为何下面代码却不会阻塞线程???(待解决???)
//在ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.myTextView = [[UITextView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height/2.0)];
[self.view addSubview:self.myTextView];
self.myTextView.text = @"Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet Lorem ipsum dolor sit er elit lamet";
self.myTextView.delegate = self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self timerDemo01];
}
- (void)timerDemo01{
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"fire in home -- %@ - %@",[[NSRunLoop currentRunLoop] currentMode],[NSThread currentThread]);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop]run];
}
#pragma mark - UITextViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"scrollViewDidScroll:%@ - %@",[[NSRunLoop currentRunLoop]currentMode],[NSThread currentThread]);
}
5.Timer与runloop
从代码doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode))看出,当前runloop模式等于传入的model,或者是runloop模式是组合模式kCFRunLoopCommonModes,doit=YES,就会执行。
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
// ...
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
} else {
doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
}
if (!doit) prev = curr;
if (doit) {
if (prev) prev->_next = item;
if (curr == head) head = item;
if (curr == tail) tail = prev;
void (^block)(void) = curr->_block;
CFRelease(curr->_mode);
free(curr);
if (doit) {
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
did = true;
}
Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
if (head) {
tail->_next = rl->_blocks_head;
rl->_blocks_head = head;
if (!rl->_blocks_tail) rl->_blocks_tail = tail;
}
return did;
}
6.RunLoop的应用
- 卡顿检测?
- FPS检测 (参考:WSL_FPS)
- 线程保活
7.如何判断主线程卡顿?
(来源:iOS App卡顿监控)
NSRunLoop的调用主要在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之间,以及kCFRunLoopAfterWaiting之后。因此,若是发现这个两个时间内耗时过长,就可以判定此时主线程出现卡顿情况。
- 使用CFRunLoopObserverRef,实时获得这些状态值的变化,如下:
/// RunLoop状态观察回调
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
<#MyClass#> *object = (__bridge <#MyClass#>*)info;
// 记录状态值
object->activity = activity;
}
/// 注册RunLoop状态观察
- (void)registerRunLoopObserver {
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
- RunLoop耗时计算
另外开启一个线程,实时计算两个状态区域之间的耗时,是否达到阈值。 dispatch_semaphore_t让子线程更及时地获知主线程NSRunLoop状态变化
卡顿覆盖范围:多次连续小卡顿、单次长时间卡顿
/// RunLoop状态观察回调
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
<#MyClass#> *object = (__bridge <#MyClass#>*)info;
// 记录状态值
object->activity = activity;
// 发送信号
dispatch_semaphore_t semaphore = object->semaphore;
dispatch_semaphore_signal(semaphore);
}
/// 注册RunLoop状态观察,并计算是否卡顿
- (void) registerRunLoopObserver {
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
CFRunLoopObserverRef 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 (++timeoutCount < 5) {
continue;
}
// 发现卡顿
NSLog(@"卡、卡、卡、顿、顿、了");
}
}
timeoutCount = 0;
}
});
}