caton
The observer registers with RunLoop to receive notifications in the following stages
kCFRunLoopEntry
kCFRunLoopBeforeTimers
kCFRunLoopBeforeSources
kCFRunLoopBeforeWaiting
kCFRunLoopAfterWaiting
kCFRunLoopExit
kCFRunLoopAllActivities
__CFRunLoopRun source code analysis can be seen in this blog, according to the source extract Runloop main flow as follows:
The event sources processed by RunLoop are categorized
The total number of event sources handled by RunLoop can actually be divided into two categories: source0 and MachPort-based messages.
Normally UIView event handling and block calls defined in the code belong to source0, while messages between threads are communicated via the GCD party
Tasks sent to the main thread and NSTimer are implemented based on MachPort messages. So in the flow chart, I have the second type
Stand out, which means they are a large type. In the official documentation, it is divided into Timer, Source, Oberserver, and source
For source0 and source1, separate out the timer.
What states of RunLoop are worth paying attention to
The state of RunLoop at source0 before the task is executed is KCFRunLoopBeforeSources
The state of RunLoop before machPort-based task execution is kCFRunLoopBeforeSources or
KCFRunLoopAfterWaiting so only need to listen for the status of Runloop in CFRunLoopBeforeSources and
CFRunLoopAfterWaiting whether the two states generate the execution time of the specified time can determine whether the delay occurs.
After CFRunLoopBeforeWaiting, the Runloop goes to sleep and has no logic to process other event sources.
If this state is monitored, it will be judged to be stalled when the main thread is sleeping, resulting in false positives.
Monitor the status of the main thread Runloop
Start a child thread that detects the status of the main thread Runloop. Passes each time the Runloop status changes
Dispatch_semaphore_signal sends a signal that increases the value of the semaphore by 1. Let the child threads keep an eye on semaphore changes,
If the main thread stays in one of the Runloop states for too long, semaphore blocks the child thread until it times out, which is a stall.
// Global or member variable CFRunLoopActivity currentActivity; // dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); / / test the main thread state Runloop CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler (kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { if (self ! = nil && semaphore ! = NULL) { currentActivity = activity; dispatch_semaphore_signal(semaphore); }}); CFRunLoopAddObserver(CFRunLoopGetMain(), observerRef, kCFRunLoopCommonModes); CFRelease(observerRef); // Use the semaphore to calculate whether the execution in a certain state has exceeded the specified time, here is 1s while (! self.cancelled) { long status = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC)); if (status ! = 0) { if (self.cancelled || semaphore == NULL) { return; } if (currentActivity == kCFRunLoopBeforeSources || currentActivity == kCFRunLoopAfterWaiting ) { if (self.cancelled) return; NSLog(@" stuck "); if (self.cancelled) return; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); }}}Copy the code
Finally, a diagram shows the whole process:
FPS
CADisplayLink is a timer that synchronizes with the screen refresh to calculate the screen frame rate in real time.
Ideally, at 60 frames per second, the CADdisplayLink callback interval is 1/60 ≈ 0.0167 seconds.
During each iteration of the Runloop, if there are time-consuming tasks, the CADdisplayLink callback time will be increased, relatively speaking, calculated
The frame rate will be lower. A frame hop occurs while the screen is drawing.
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(p_tick:)]; [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; - (void)p_tick:(CADisplayLink *)link { self.count++; if (! self.lastTime) { self.lastTime = CACurrentMediaTime(); } else { NSTimeInterval currentTime = CACurrentMediaTime(); NSTimeInterval duration = currentTime - self.lastTime; if (duration > 1) { NSString *fps = [NSString stringWithFormat:@"%.2f", (self.count / duration)]; Self.fpslabel. text = [NSString stringWithFormat:@" frame rate: %@", FPS]; self.count = self.lastTime = 0; }}}Copy the code