1 卡顿原因及解决方案
可能的原因:
- 长时间的主线程同步任务,例如大量数据的计算、I/O 操作或网络请求
- 复杂UI布局,例如图文混排
- 资源竞争,多个线程同时访问共享资源时,如果没有合适地加锁或使用其他同步机制
- 主线程被其他高优先级任务占用时,例如系统任务或后台任务
解决方案:
- 使用 Instruments 工具进行性能分析和检测。
- 在主线程上避免执行耗时操作,尤其是 I/O 操作、网络请求等,可以将这些操作放到后台线程或使用异步方式执行。
- 合理分段长时间运行的任务,避免长时间的单次执行。
- 在 UI 操作方面,尽量减少不必要的 UI 更新操作,使用合适的方式优化界面性能。
- 使用 GCD 或 Operation Queue 等多线程技术,合理管理并发任务,避免资源竞争和死锁。
- 在主线程中优先处理用户交互事件和 UI 更新,避免被其他低优先级任务占用。
2 卡顿检测
主流方案:主线程卡顿监控。通过开辟一个子线程来监控主线程的 RunLoop,当两个状态区域之间的耗时大于阈值时,就记为发生一次卡顿。
实现思路:开辟一个子线程,然后实时计算 kCFRunLoopBeforeSources
和 kCFRunLoopAfterWaiting
两个状态区域之间的耗时是否超过某个阀值,来断定主线程的卡顿情况,如果主线程发生卡顿,这时我们要保存应用的上下文,即卡顿发生时程序的堆栈调用和运行日志上传。
2.1NSRunLoop 实现
#import <Foundation/Foundation.h>
@interface LagMonitor : NSObject
+ (instancetype)sharedInstance;
- (void)startMonitoring;
@end
@implementation LagMonitor {
id _observer;
dispatch_semaphore_t _semaphore;
BOOL _isMonitoring;
}
+ (instancetype)sharedInstance {
static LagMonitor *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[LagMonitor alloc] init];
});
return instance;
}
- (void)startMonitoring {
if (_isMonitoring) {
return;
}
_isMonitoring = YES;
// 创建信号量,用于控制RunLoop监测的时间间隔
_semaphore = dispatch_semaphore_create(0);
// 创建观察者,监听RunLoop的各个阶段
_observer = [NSRunLoopObserver
observerWithActivity:NSRunLoopAllActivities
repeats:YES
callback:^(NSRunLoopObserver *observer, NSRunLoopActivity activity) {
[self runLoopObserverCallback];
}];
if (_observer) {
// 将观察者添加到主线程的RunLoop中
[[NSRunLoop mainRunLoop] addObserver:_observer];
// 创建一个子线程用于监测RunLoop的状态
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (_isMonitoring) { // 等待信号量,即等待指定的时间间隔
long semaphoreWait = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
if (semaphoreWait != 0) {
// 如果信号量等待超时,则认为主线程出现卡顿
[BacktraceLogger printMainThreadStack];
}
}
});
} else {
NSLog(@"创建 NSRunLoopObserver 失败");
}
}
- (void)runLoopObserverCallback {
// 发送信号量,通知子线程主线程的RunLoop正在运行
dispatch_semaphore_signal(_semaphore);
}
@end
2.2CFRunLoopRef 实现
#import <Foundation/Foundation.h>
@interface LagMonitor : NSObject
+ (instancetype)sharedInstance;
- (void)startMonitoring;
@end
@implementation LagMonitor {
CFRunLoopObserverRef _observer;
dispatch_semaphore_t _semaphore;
BOOL _isMonitoring;
}
+ (instancetype)sharedInstance {
static LagMonitor *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[LagMonitor alloc] init];
});
return instance;
}
- (void)startMonitoring {
if (_isMonitoring) {
return;
}
_isMonitoring = YES;
// 创建信号量,用于控制RunLoop监测的时间间隔
_semaphore = dispatch_semaphore_create(0);
// 创建观察者,监听RunLoop的各个阶段
CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallback, &context);
if (_observer) {
// 将观察者添加到主线程的RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
// 创建一个子线程用于监测RunLoop的状态
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (_isMonitoring) { // 等待信号量,即等待指定的时间间隔
long semaphoreWait = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
if (semaphoreWait != 0) {
// 如果信号量等待超时,则认为主线程出现卡顿
[BacktraceLogger printMainThreadStack];
}
}
});
} else {
NSLog(@"创建 CFRunLoopObserverRef 失败");
}
}
void runLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
LagMonitor *monitor = (__bridge LagMonitor *)info;
// 发送信号量,通知子线程主线程的RunLoop正在运行
dispatch_semaphore_signal(monitor->_semaphore);
}
@end
2.3 打印信息
import Foundation
class BacktraceLogger {
// 在需要时打印主线程的堆栈信息
static func printMainThreadStack() {
if Thread.isMainThread {
if let callStackSymbols = Thread.callStackSymbols as? [String] {
print("Main Thread Stack Trace:")
for symbol in callStackSymbols {
print(symbol)
}
}
} else {
DispatchQueue.main.async {
printMainThreadStack()
}
}
}
// 在程序启动时开始监控崩溃
static func startMonitoringCrashes() {
NSSetUncaughtExceptionHandler { exception in
print("Crash Detected:")
print(exception)
print(exception.callStackSymbols.joined(separator: "n"))
}
}
}
2.4 使用示例
// 开始监测主线程的卡顿情况
[[LagMonitor sharedInstance] startMonitoring];
// 模拟主线程执行任务,可以在这里进行一些耗时操作
// 这里只是一个简单的示例,实际应用中需要根据具体情况进行调整
for (NSInteger i = 0; i < 1000000000; i++) {
NSLog(@"执行任务 %ld", (long)i);
}
2.5 具体方案
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
MyClass *object = (__bridge MyClass*)info;
// 记录状态值
object->activity = activity;
// 发送信号
dispatch_semaphore_t semaphore = moniotr->semaphore;
dispatch_semaphore_signal(semaphore);
}
- (void)registerObserver
{
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;
// 检测到卡顿,进行卡顿上报
}
}
timeoutCount = 0;
}
});
}