RunLoop在實際開中的應(yīng)用
- 解決NSTimer在滑動時停止工作的問題
- 控制線程生命周期(線程保活)
- 監(jiān)控應(yīng)用卡頓
- 性能優(yōu)化
一、解決NSTimer在滑動時停止工作的問題
- 在拖拽時定時器不工作
__block int count = 0;
// 默認(rèn)添加到default模式
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
打印結(jié)果

- 在拖拽時人仍然正常工作
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并不是一個真的模式,它只是一個標(biāo)記
// timer能在_commonModes數(shù)組中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

二 線程?;?/h5>
我們先封裝一個長久活命的線程
PermanentThread.h
// 聲明一個block - 用于執(zhí)行任務(wù)
typedef void(^PermanentThreadTask)(void);
/** 線程?;?*/
@interface PermanentThread : NSObject
// 在當(dāng)前線程執(zhí)行一個任務(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只有添加事件才會執(zhí)行
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
// 當(dāng)當(dāng)前對象存在并且變量為false的時候,才一直執(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];
}
運行結(jié)果

三、讓UITableView、UICollectionView延遲加載圖片
首先創(chuàng)建一個單例,單例中定義了幾個數(shù)組,用來存要在runloop循環(huán)中執(zhí)行的任務(wù),然后為主線程的runloop添加一個CFRunLoopObserver,當(dāng)主線程在NSDefaultRunLoopMode中執(zhí)行完任務(wù),即將睡眠前,執(zhí)行一個單例中保存的一次圖片渲染任務(wù)。關(guān)鍵代碼看 RunLoopWorkDistribution即可。
四、監(jiān)測主線程的卡頓,并將卡頓時的線程堆棧信息保存下來,選擇合適時機(jī)上傳到服務(wù)器
第一步,創(chuà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)測時,往主線程的RunLoop中添加一個observer,并往子線程中添加一個定時器,每0.5秒檢測一次耗時的時長。
- (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)建一個timer,并添加到子線程的RunLoop中
[self performSelector:@selector(addTimerToMonitorThread) onThread:self.monitorThread withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
}
- (void)addTimerToMonitorThread
{
if (_timer) {
return;
}
// 創(chuàng)建一個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)的時間可能會非常短,有時候只有1毫秒,有時候甚至1毫秒都不到,靜止不動時,則會長時間進(jìn)入睡覺狀態(tài)。
因為主線程中的block、交互事件、以及其他任務(wù)都是在kCFRunLoopBeforeSources 到 kCFRunLoopBeforeWaiting 之前執(zhí)行,所以我在即將開始執(zhí)行Sources 時,記錄一下時間,并把正在執(zhí)行任務(wù)的標(biāo)記置為YES,將要進(jìn)入睡眠狀態(tài)時,將正在執(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í)行完,那就需要計算時間差
NSTimeInterval excuteTime = [[NSDate date] timeIntervalSinceDate:monitor.startDate];
NSLog(@"定時器---%@",[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)在的時間大于闕值,則把堆棧信息保存下來,便于后面處理。
為了能夠捕獲到堆棧信息,我把timer的間隔調(diào)的很?。?.01),而評定為卡頓的闕值也調(diào)的很?。?.01)。 實際使用時這兩個值應(yīng)該是比較大,timer間隔為1s,卡頓闕值為2s即可。