RunLoop在實(shí)際開中的應(yīng)用
- 解決NSTimer在滑動(dòng)時(shí)停止工作的問題
- 控制線程生命周期(線程保活)
- 監(jiān)控應(yīng)用卡頓
- 性能優(yōu)化
一列肢、解決NSTimer在滑動(dòng)時(shí)停止工作的問題
- 在拖拽時(shí)定時(shí)器不工作
__block int count = 0;
// 默認(rèn)添加到default模式
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
打印結(jié)果
- 在拖拽時(shí)人仍然正常工作
static int count = 0;
// 2.添加到指定模式下
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// NSDefaultRunLoopMode建车、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一個(gè)真的模式促王,它只是一個(gè)標(biāo)記
// timer能在_commonModes數(shù)組中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
二 線程甭鞔螅活
我們先封裝一個(gè)長(zhǎng)久活命的線程
PermanentThread.h
// 聲明一個(gè)block - 用于執(zhí)行任務(wù)
typedef void(^PermanentThreadTask)(void);
/** 線程毕德澹活 */
@interface PermanentThread : NSObject
// 在當(dāng)前線程執(zhí)行一個(gè)任務(wù)
- (void)executeTask:(PermanentThreadTask)task;
// 結(jié)束線程
- (void)stop;
@end
PermanentThread.m
/** CSThread **/
@interface CSThread : NSThread
@end
@implementation CSThread
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
@interface PermanentThread()
/** 線程*/
@property(nonatomic,strong)CSThread *thread;
/** 是否停止*/
@property(nonatomic,assign, getter=isStopped)BOOL stopped;
@end
@implementation PermanentThread
// 初始化方法
- (instancetype)init {
self = [super init];
if (self) {
self.stopped = NO;
// 初始化線程
__weak typeof(self) weakSelf = self;
self.thread = [[CSThread alloc] initWithBlock:^{
// runloop只有添加事件才會(huì)執(zhí)行
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
// 當(dāng)當(dāng)前對(duì)象存在并且變量為false的時(shí)候,才一直執(zhí)行
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
// 開啟線程
[self.thread start];
}
return self;
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - public method
// 執(zhí)行任務(wù)
- (void)executeTask:(PermanentThreadTask)task {
// 如果線程釋放或者無任務(wù),則退出
if (!self.thread || !task) {
return;
}
// 開始執(zhí)行任務(wù)
[self performSelector:@selector(innerExecuteTask:) onThread:self.thread withObject:task waitUntilDone:NO];
}
// 停止
- (void)stop {
if (!self.thread) {
return;
}
[self performSelector:@selector(innerStop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
#pragma mark - private method
// 執(zhí)行任務(wù)
- (void)innerExecuteTask:(PermanentThreadTask)task {
task();
}
// 停止線程 runloop
- (void)innerStop {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
}
@end
外部調(diào)用
- (void)viewDidLoad {
[super viewDidLoad];
// 2.線程笨⌒裕活
self.thread = [[PermanentThread alloc] init];
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.thread executeTask:^{
NSLog(@"執(zhí)行任務(wù) - %@", [NSThread currentThread]);
}];
}
- (void)stopBtnClick {
[self.thread stop];
}
運(yùn)行結(jié)果
三、讓UITableView描扯、UICollectionView延遲加載圖片
首先創(chuàng)建一個(gè)單例定页,單例中定義了幾個(gè)數(shù)組,用來存要在runloop循環(huán)中執(zhí)行的任務(wù)绽诚,然后為主線程的runloop添加一個(gè)CFRunLoopObserver,當(dāng)主線程在NSDefaultRunLoopMode中執(zhí)行完任務(wù)典徊,即將睡眠前杭煎,執(zhí)行一個(gè)單例中保存的一次圖片渲染任務(wù)。關(guān)鍵代碼看 RunLoopWorkDistribution即可卒落。
四羡铲、監(jiān)測(cè)主線程的卡頓,并將卡頓時(shí)的線程堆棧信息保存下來儡毕,選擇合適時(shí)機(jī)上傳到服務(wù)器
第一步也切,創(chuàng)建一個(gè)子線程,在線程啟動(dòng)時(shí)腰湾,啟動(dòng)其RunLoop雷恃。
+ (instancetype)shareMonitor
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
instance.monitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitorThreadEntryPoint) object:nil];
[instance.monitorThread start];
});
return instance;
}
+ (void)monitorThreadEntryPoint
{
@autoreleasepool {
[[NSThread currentThread] setName:@"FluencyMonitor"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
第二步,在開始監(jiān)測(cè)時(shí)檐盟,往主線程的RunLoop中添加一個(gè)observer褂萧,并往子線程中添加一個(gè)定時(shí)器,每0.5秒檢測(cè)一次耗時(shí)的時(shí)長(zhǎng)葵萎。
- (void)start
{
if (_observer) {
return;
}
// 1.創(chuàng)建observer CFRunLoopObserverContext context = {0,(__bridge void*)self, NULL, NULL, NULL};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
// 2.將observer添加到主線程的RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
// 3.創(chuàng)建一個(gè)timer导犹,并添加到子線程的RunLoop中
[self performSelector:@selector(addTimerToMonitorThread) onThread:self.monitorThread withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
}
- (void)addTimerToMonitorThread
{
if (_timer) {
return;
}
// 創(chuàng)建一個(gè)timer
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.01, 0, 0, &runLoopTimerCallBack, &context);
// 添加到子線程的RunLoop中
CFRunLoopAddTimer(currentRunLoop, _timer, kCFRunLoopCommonModes);
}
第三步,補(bǔ)充觀察者回調(diào)處理
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
FluencyMonitor *monitor = (__bridge FluencyMonitor*)info;
NSLog(@"MainRunLoop---%@",[NSThread currentThread]);
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
monitor.startDate = [NSDate date];
monitor.excuting = YES;
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
monitor.excuting = NO;
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
RunLoop進(jìn)入睡眠狀態(tài)的時(shí)間可能會(huì)非常短羡忘,有時(shí)候只有1毫秒谎痢,有時(shí)候甚至1毫秒都不到,靜止不動(dòng)時(shí)卷雕,則會(huì)長(zhǎng)時(shí)間進(jìn)入睡覺狀態(tài)节猿。
因?yàn)橹骶€程中的block、交互事件漫雕、以及其他任務(wù)都是在kCFRunLoopBeforeSources 到 kCFRunLoopBeforeWaiting 之前執(zhí)行滨嘱,所以我在即將開始執(zhí)行Sources 時(shí),記錄一下時(shí)間浸间,并把正在執(zhí)行任務(wù)的標(biāo)記置為YES太雨,將要進(jìn)入睡眠狀態(tài)時(shí),將正在執(zhí)行任務(wù)的標(biāo)記置為NO魁蒜。
第四步囊扳,補(bǔ)充timer 的回調(diào)處理
static void runLoopTimerCallBack(CFRunLoopTimerRef timer, void *info)
{
FluencyMonitor *monitor = (__bridge FluencyMonitor*)info;
if (!monitor.excuting) {
return;
}
// 如果主線程正在執(zhí)行任務(wù),并且這一次loop 執(zhí)行到 現(xiàn)在還沒執(zhí)行完兜看,那就需要計(jì)算時(shí)間差
NSTimeInterval excuteTime = [[NSDate date] timeIntervalSinceDate:monitor.startDate];
NSLog(@"定時(shí)器---%@",[NSThread currentThread]);
NSLog(@"主線程執(zhí)行了---%f秒",excuteTime);
if (excuteTime >= 0.01) {
NSLog(@"線程卡頓了%f秒",excuteTime);
[monitor handleStackInfo];
}
}
timer 每 0.01秒執(zhí)行一次锥咸,如果當(dāng)前正在執(zhí)行任務(wù)的狀態(tài)為YES,并且從開始執(zhí)行到現(xiàn)在的時(shí)間大于闕值细移,則把堆棧信息保存下來搏予,便于后面處理。
為了能夠捕獲到堆棧信息弧轧,我把timer的間隔調(diào)的很醒┙摹(0.01)球涛,而評(píng)定為卡頓的闕值也調(diào)的很小(0.01)校镐。 實(shí)際使用時(shí)這兩個(gè)值應(yīng)該是比較大亿扁,timer間隔為1s,卡頓闕值為2s即可鸟廓。