App在线运行的时候发生了卡顿,是很难了解卡顿原因的。
一个相对比较有用的办法是做一个常驻线程,定时抓取主线程的运行时状态,当主线程的运行时状态在几个周期里总是处于同一个状态/或同一类状态时,则大概率认为发生了卡顿,此时使用CrashReporter这个第三方组件模拟一个crash获取到对应的call stack就好对问题进行跟进了。
至于程序员怎么拿到call stack进行分析,则各有各的办法,有些会自己搭建一个后台服务,将call stack信息做上传,我们就不想搞那么多东西,直接上传到Bugfender了,实时查看。
代码内容是参考了一些blog之后写的,具体的文章确实已经记不起了,以下将我用到的代码写下,以做为备忘。
#import <Foundation/Foundation.h>
@interface PerformanceMonitor : NSObject
+ (instancetype)sharedInstance;
- (void)start;
- (void)stop;
@end
#import "PerformanceMonitor.h"
#import <CrashReporter/CrashReporter.h>
#import "DDFileLogger.h"
static int kTimeout = 1000;//单次定时器触发时间,1000毫秒
static int kTimeoutCount = 3;//定时器触发次数,总时间为timeout * timeoutCount
@interface PerformanceMonitor ()
@property (nonatomic, assign) int timeoutCount;
@property (nonatomic, assign) CFRunLoopObserverRef observer;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) CFRunLoopActivity activity;
@end
@implementation PerformanceMonitor
+ (instancetype)sharedInstance {
static id instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
PerformanceMonitor *moniotr = (__bridge PerformanceMonitor*)info;
moniotr.activity = activity;
dispatch_semaphore_t semaphore = moniotr.semaphore;
dispatch_semaphore_signal(semaphore);
}
- (void)stop {
if (!_observer)
return;
CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
CFRelease(_observer);
_observer = NULL;
}
- (void)start {
if (_observer)
return;
// 信号
_semaphore = dispatch_semaphore_create(0);
// 注册RunLoop状态观察
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
WS(ws);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES) {
long st = dispatch_semaphore_wait(ws.semaphore, dispatch_time(DISPATCH_TIME_NOW, kTimeout*NSEC_PER_MSEC));
if (st != 0) {
if (!ws.observer) {
ws.timeoutCount = 0;
ws.semaphore = 0;
ws.activity = 0;
return;
}
if (ws.activity == kCFRunLoopBeforeSources || ws.activity == kCFRunLoopAfterWaiting) {
if (++ws.timeoutCount < kTimeoutCount)
continue;
PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
NSData *data = [crashReporter generateLiveReport];
PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"------------\n%@\n------------", report);
NSString* uin = [UserDefaultHelper myUINString];
NSString* version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
NSString* build = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
NSString* title = [NSString stringWithFormat:@"%@_%@.%@",uin,version,build];
[Bugfender sendIssueWithTitle:title text:report];
}
}
ws.timeoutCount = 0;
}
});
}
@end