Painted painted levels: fostered fostered fostered

Tags: iOS performance Monitoring Tools RunLoop


* preface:

Recently, I learned a lot from Mr. Daiming’s technical sharing about “performance monitoring”. Based on recent learning, this article summarizes some performance monitoring related practices and plans to write a series of performance monitoring related articles. * The following directories are available: iOS performance monitoring (1) CPU power monitoring iOS performance monitoring (2) Main thread lag monitoring iOS performance monitoring (3) Method time monitoring


This article will introduce the iOS performance monitoring tool (QiLagMonitor) and “thread lag monitoring” related functional modules.

Understand the state of threads

The main thread runloop registers five modes by default: KCFRunLoopDefaultMode, UITrackingRunLoopMode, UIInitializationRunLoopMode, GSEventReceiveRunLoopMode, kCFRunLoopCommonModes.

The name of the role
kCFRunLoopDefaultMode The default Mode of the App in which the main thread is normally run.
UITrackingRunLoopMode Interface tracking Mode, used for ScrollView tracking touch sliding, to ensure that the interface sliding is not affected by other modes.
UIInitializationRunLoopMode The first Mode entered at the beginning of App startup will not be used after the completion of startup.
GSEventReceiveRunLoopMode Internal modes that accept system events are usually not needed.
kCFRunLoopCommonModes This is a placeholder Mode. In fact, it is used to switch between Default mode and UI mode.

APPLE publicly provides two modes: NSDefaultRunLoopMode (kCFRunLoopDefaultMode) and NSRunLoopCommonModes (kCFRunLoopCommonModes).

In the next section, NSRunLoopCommonModes are used to monitor the main thread.

Then, runloop Observer: The Runloop Observer has seven states.

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

Respectively represent:

state meaning
kCFRunLoopEntry Entry to run runloop.
kCFRunLoopBeforeTimers Inside the event processing loop before any timers are processedTimerBefore the timer)
kCFRunLoopBeforeSources Inside the event processing loop before any sources are processedSourcesBefore the source)
kCFRunLoopBeforeWaiting Inside the event processing loop before the run loop sleeps, waiting for a source or timer to fire. This activity does not occur if CFRunLoopRunInMode is called with a timeout of 0 Seconds. It also does not occur in a particular iteration of the event processing loop if a version 0 source firesSourceAnd the timerTimerBefore)
kCFRunLoopAfterWaiting Inside the event processing loop after the run loop wakes up, but before processing the event that woke it up. This activity occurs only if the run loop did in fact go to sleep During the current loopSourceAnd the timerTimerAfter, and before being awakened.
kCFRunLoopExit The exit of the run loop, after exiting the event processing loop. This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode. (Exit of runloop)
kCFRunLoopAllActivities All states of runloop.

PS: For more information on Runloop, check out bin’s previous blog.

How does iOS monitor thread congestion?

Talk about the general implementation ideas in QiLagMonitor.

  • First, create an observerrunLoopObserver, used to observe the main threadrunloopState.

A dispatchSemaphore is also created to ensure synchronous operations.

  • Second, add the runLoopObserver to the main thread runloop to observe.

  • Then, a child thread is opened and a continuous loop is opened in the child thread to monitor the status of the main thread runloop.

  • If the status of the main thread runloop is stuck in BeforeSources or AfterWaiting for more than 88 milliseconds, the main thread is currently stuck.

In this case, we save the main thread’s current call stack for monitoring purposes.

Schematic principle:

  • Under normal conditions:

  • Under abnormal circumstances:

Iii. Specific implementation of QiLagMonitor

  • First, create a semaphoredispatchSemaphoreAnd the observerrunLoopObserver.
/ /! DispatchSemaphore = dispatch_semaphoRE_create (0); / /! Dispatch Semaphore // CFRunLoopObserverContext Context = {0,(__bridge void*)self,NULL,NULL}; runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);Copy the code

Meanwhile, when the main thread’s runloop status changes, the runLoopObserverCallBack method is called, which stores the current Runloop status internally. At the same time, control semaphore.

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    QiLagMonitor *lagMonitor = (__bridge QiLagMonitor*)info;
    lagMonitor->runLoopActivity = activity;
    
    dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
    dispatch_semaphore_signal(semaphore);
}
Copy the code
  • Second, add the observer to the main threadrunloopthecommonObserve in mode.
/ /! CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);Copy the code
  • Third, create a child thread and start a persistent threadloop(Actually, it’s awhileInfinite loop) to monitor the main threadrunloopState. whenrunloopThe state of continues to beBeforeSources,AfterWaitingIf the status is two, the main thread is stuck, and the current main thread call stack is recorded.
/ /! Dispatch_async (dispatch_get_global_queue(0, 0), ^{//! While (YES) {long semaphoreWait = dispatch_semaphore_wait(self->dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, STUCKMONITORRATE * NSEC_PER_MSEC)); if (semaphoreWait ! = 0) { if (! self->runLoopObserver) { self->timeoutCount = 0; self->dispatchSemaphore = 0; self->runLoopActivity = 0; return; } / /! The state of the two runloops, BeforeSources and AfterWaiting can detect whether the two state interval time caton the if (self - > runLoopActivity = = kCFRunLoopBeforeSources | | self->runLoopActivity == kCFRunLoopAfterWaiting) { //! If (++self->timeoutCount < 3) {continue; } NSLog(@"monitor trigger"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSString *stackStr = [QiCallStack callStackWithType:QiCallStackTypeMain]; QiCallStackModel *model = [[QiCallStackModel alloc] init]; model.stackStr = stackStr; model.isStuck = YES; [[[QiLagDB shareInstance] increaseWithStackModel:model] subscribeNext:^(id x) {}]; }); } // end activity }// end semaphore wait self->timeoutCount = 0; }// end while });Copy the code

Recommended articles:

New Year’s Day benefits! Add animation to the view with SwiftUI. Write a simple page with SwiftUI. Optimize iOS App launch Use the “Time Profiler” tool to monitor the startup Time of your App. Understand the startup process of your App