• Caton principle of production

  • How to collect Caton

  • Use bugly, cloud and other third-party collection

  • Collect your own carton

  • Monitor the main thread RunLoop

  • The child thread ping

Caton principle of production

FPS (Frames Per Second) refers to the number of Frames rendered Per Second, which is usually used to measure the smoothness of a picture. The more Frames Per Second, the smoother the picture. Generally 60 is the threshold, and if the mainline FPS is below 60, the application may stall.

How to collect Caton

Use bugly, cloud and other third-party collection

There are many third-party websites in China that can be used to collect cardloads, such as Bugly and Tunyun. I recommend you to use Tencent’s Bugly to collect Katon.

Collect your own carton

If we want to manually monitor caton ourselves, there are several options, as follows:

Monitor the main thread RunLoop

We know that iOS apps run on RunLoop, so let’s take a look at RunLoop simplified code.

// 1. Enter loop__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled)// 2. Runloop is about to trigger the Timer callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); RunLoop is about to trigger the Source0 (non-port) callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); RunLoop triggers the Source0 (non-port) callback. sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle)// 5. Execute added block__CFRunLoopDoBlocks(runloop, currentMode); // 6. The RunLoop thread is about to go to sleep. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); // 7. Call mach_msg and wait for the message to accept mach_port. The thread will sleep until it is awakened by one of the following events. __CFRunLoopServiceMachPort(waitSet, & MSG, sizeof(MSg_buffer), &livePort)// Went to sleep // 8. The thread of RunLoop was just woken up. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting// 9. If a Timer runs out of time, the Timer's callback __CFRunLoopDoTimers(runloop, currentMode, mach_Absolute_time ())// 10 is triggered. If there are blocks dispatched to main_queue, bloc __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); __CFRunLoopDoSource1(runloop, currentMode, Source1, MSG); // 12.RunLoop will exit __CFRunLoopDoObservers(rL, currentMode, kCFRunLoopExit);Copy the code

We can see that the RunLoop call methods are concentrated between kCFRunLoopBeforeSources and kCFRunLoopAfterWaiting. We can create a child thread to monitor the main thread RunLoop, and then calculate in real time whether the time between kCFRunLoopBeforeSources and kCFRunLoopAfterWaiting exceeds a certain threshold to determine whether the main thread is running late. For example, if the timeout time is 50ms for five consecutive times, a stall is considered to have occurred. The code is as follows:

@interface AKStuckMonitor (){ int timeoutCount; CFRunLoopObserverRef observer; @public dispatch_semaphore_t semaphore; CFRunLoopActivity activity; }@end@implementation FQLAPMStuckMonitor+ (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){ AKStuckMonitor *moniotr = (__bridge AKStuckMonitor*)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); CFRunLoopObserverContext Context = {0,(__bridge void*)self,NULL,NULL}; observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); // Dispatch_async (dispatch_get_global_queue(0, 0), ^{float time = 50; while (YES) { long st = dispatch_semaphore_wait(self->semaphore, dispatch_time(DISPATCH_TIME_NOW, time * NSEC_PER_MSEC)); if (st ! = 0) { if (! self->observer) { self->timeoutCount = 0; self->semaphore = 0; self->activity = 0; return; } if (self->activity==kCFRunLoopBeforeSources || self->activity==kCFRunLoopAfterWaiting) { if (++self->timeoutCount < 5)  continue; NSlog(@" delay detected "); } } self->timeoutCount = 0; }}); }@endCopy the code

The child thread ping

Create a child thread to ping the main thread through the semaphore, set the flag bit to YES on each check, and then send a task to the main thread with the flag bit set to NO. Then the child thread sleeps the timeout threshold and determines whether the flag bit is successfully set to NO. If it does not indicate that the main thread has stalled.

@interface PingThread : NSThread...... @end@implementation PingThread- (void)main { [self pingMainThread]; }- (void)pingMainThread { while (! self.cancelled) { @autoreleasepool { dispatch_async(dispatch_get_main_queue(), ^{ [_lock unlock]; }); CFAbsoluteTime pingTime = CFAbsoluteTimeGetCurrent(); [_lock lock]; if (CFAbsoluteTimeGetCurrent() - pingTime >= _threshold) { ...... } [NSThread sleepForTimeInterval: _interval]; } }}@endCopy the code

IOS development exchange technology group: 563513413, no matter you are big bull or small white are welcome to enter, share BAT, Ali interview questions, interview experience, discuss technology, we exchange learning and growth together!