mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
559 lines
23 KiB
Objective-C
559 lines
23 KiB
Objective-C
// Copyright 2020 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker.h"
|
|
#import "FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker+Private.h"
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
|
|
|
|
NSString *const kFPRPrefixForScreenTraceName = @"_st_";
|
|
NSString *const kFPRFrozenFrameCounterName = @"_fr_fzn";
|
|
NSString *const kFPRSlowFrameCounterName = @"_fr_slo";
|
|
NSString *const kFPRTotalFramesCounterName = @"_fr_tot";
|
|
|
|
// Note: This was previously 60 FPS, but that resulted in 90% + of all frames collected to be
|
|
// flagged as slow frames, and so the threshold for iOS is 59 FPS.
|
|
// b/545164487 tracks any changes / next steps for ProMotion devices - where the frame rate can be
|
|
// as high as 120 FPS.
|
|
CFTimeInterval const kFPRSlowFrameThreshold = 1.0 / 59.0; // Anything less than 59 FPS is slow.
|
|
CFTimeInterval const kFPRFrozenFrameThreshold = 700.0 / 1000.0;
|
|
|
|
#if TARGET_OS_TV
|
|
/** Default/fallback FPS value used when UIScreen.maximumFramesPerSecond is unavailable or invalid.
|
|
*/
|
|
static const NSInteger kFPRDefaultFPS = 60;
|
|
|
|
/** Epsilon value to avoid floating point comparison issues (e.g., 59.94 vs 60) on tvOS. */
|
|
static const CFTimeInterval kFPRSlowFrameEpsilon = 0.001;
|
|
#endif
|
|
|
|
/** Constant that indicates an invalid time. */
|
|
CFAbsoluteTime const kFPRInvalidTime = -1.0;
|
|
|
|
/** Returns the class name without the prefixed module name present in Swift classes
|
|
* (e.g. MyModule.MyViewController -> MyViewController).
|
|
*/
|
|
static NSString *FPRUnprefixedClassName(Class theClass) {
|
|
NSString *className = NSStringFromClass(theClass);
|
|
NSRange periodRange = [className rangeOfString:@"." options:NSBackwardsSearch];
|
|
if (periodRange.location == NSNotFound) {
|
|
return className;
|
|
}
|
|
return periodRange.location < className.length - 1
|
|
? [className substringFromIndex:periodRange.location + 1]
|
|
: className;
|
|
}
|
|
|
|
/** Returns the name for the screen trace for a given UIViewController. It does the following:
|
|
* - Removes module name from swift classes - (e.g. MyModule.MyViewController -> MyViewController)
|
|
* - Prepends "_st_" to the class name
|
|
* - Truncates the length if it exceeds the maximum trace length.
|
|
*
|
|
* @param viewController The view controller whose screen trace name we want. Cannot be nil.
|
|
* @return An NSString containing the trace name, or a string containing an error if the
|
|
* class was nil.
|
|
*/
|
|
static NSString *FPRScreenTraceNameForViewController(UIViewController *viewController) {
|
|
NSString *unprefixedClassName = FPRUnprefixedClassName([viewController class]);
|
|
if (unprefixedClassName.length != 0) {
|
|
NSString *traceName =
|
|
[NSString stringWithFormat:@"%@%@", kFPRPrefixForScreenTraceName, unprefixedClassName];
|
|
return traceName.length > kFPRMaxNameLength ? [traceName substringToIndex:kFPRMaxNameLength]
|
|
: traceName;
|
|
} else {
|
|
// This is unlikely, but might happen if there's a regression on iOS where the class name
|
|
// returned for a non-nil class is nil or empty.
|
|
return @"_st_ERROR_NIL_CLASS_NAME";
|
|
}
|
|
}
|
|
|
|
@implementation FPRScreenTraceHolder
|
|
@end
|
|
|
|
@implementation FPRScreenTraceTracker {
|
|
/** Instance variable storing the total frames observed so far. */
|
|
atomic_int_fast64_t _totalFramesCount;
|
|
|
|
/** Instance variable storing the slow frames observed so far. */
|
|
atomic_int_fast64_t _slowFramesCount;
|
|
|
|
/** Instance variable storing the frozen frames observed so far. */
|
|
atomic_int_fast64_t _frozenFramesCount;
|
|
|
|
#if TARGET_OS_TV
|
|
/** Cached maximum frames per second from UIScreen on tvOS. */
|
|
NSInteger _cachedMaxFPS;
|
|
#endif
|
|
|
|
/** Cached slow frame budget. On iOS, uses kFPRSlowFrameThreshold (59 FPS).
|
|
* On tvOS, computed dynamically from UIScreen.maximumFramesPerSecond.
|
|
*/
|
|
CFTimeInterval _cachedSlowBudget;
|
|
}
|
|
|
|
@dynamic totalFramesCount;
|
|
@dynamic frozenFramesCount;
|
|
@dynamic slowFramesCount;
|
|
|
|
+ (instancetype)sharedInstance {
|
|
static FPRScreenTraceTracker *instance;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
instance = [[self alloc] init];
|
|
});
|
|
return instance;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_activeScreenTraces = [[NSMutableDictionary alloc] init];
|
|
_activeScreenTracesLock = [[NSRecursiveLock alloc] init];
|
|
|
|
_previouslyVisibleViewControllers = nil; // Will be set when there is data.
|
|
_screenTraceTrackerSerialQueue =
|
|
dispatch_queue_create("com.google.FPRScreenTraceTracker", DISPATCH_QUEUE_SERIAL);
|
|
_screenTraceTrackerDispatchGroup = dispatch_group_create();
|
|
|
|
atomic_store_explicit(&_totalFramesCount, 0, memory_order_relaxed);
|
|
atomic_store_explicit(&_frozenFramesCount, 0, memory_order_relaxed);
|
|
atomic_store_explicit(&_slowFramesCount, 0, memory_order_relaxed);
|
|
|
|
#if TARGET_OS_TV
|
|
// Initialize slow budget and maxFPS values with defaults for tvOS.
|
|
// On tvOS, refresh rate depends on the connected display mode (e.g. 50 Hz vs 60 Hz).
|
|
_cachedMaxFPS = kFPRDefaultFPS;
|
|
_cachedSlowBudget = 1.0 / (CFTimeInterval)kFPRDefaultFPS;
|
|
|
|
if ([NSThread isMainThread]) {
|
|
[self updateCachedSlowBudget];
|
|
} else {
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
[self updateCachedSlowBudget];
|
|
});
|
|
}
|
|
#else
|
|
_cachedSlowBudget = kFPRSlowFrameThreshold;
|
|
#endif
|
|
|
|
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkStep)];
|
|
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
|
|
// We don't receive background and foreground events from analytics and so we have to listen to
|
|
// them ourselves.
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(appDidBecomeActiveNotification:)
|
|
name:UIApplicationDidBecomeActiveNotification
|
|
object:[UIApplication sharedApplication]];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(appWillResignActiveNotification:)
|
|
name:UIApplicationWillResignActiveNotification
|
|
object:[UIApplication sharedApplication]];
|
|
|
|
#if TARGET_OS_TV
|
|
// On tvOS, the refresh rate can change when the user switches display modes or connects to
|
|
// different displays. Listen for mode changes to recompute the slow budget.
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(screenModeDidChangeNotification:)
|
|
name:UIScreenModeDidChangeNotification
|
|
object:nil];
|
|
#endif
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[_displayLink invalidate];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
name:UIApplicationDidBecomeActiveNotification
|
|
object:[UIApplication sharedApplication]];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
name:UIApplicationWillResignActiveNotification
|
|
object:[UIApplication sharedApplication]];
|
|
#if TARGET_OS_TV
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
name:UIScreenModeDidChangeNotification
|
|
object:nil];
|
|
#endif
|
|
}
|
|
|
|
- (void)appDidBecomeActiveNotification:(NSNotification *)notification {
|
|
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
|
|
// soon as we're notified of an event.
|
|
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
|
|
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
|
|
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
|
|
|
|
dispatch_group_async(self.screenTraceTrackerDispatchGroup, self.screenTraceTrackerSerialQueue, ^{
|
|
for (id viewController in self.previouslyVisibleViewControllers) {
|
|
[self startScreenTraceForViewController:viewController
|
|
currentTotalFrames:currentTotalFrames
|
|
currentFrozenFrames:currentFrozenFrames
|
|
currentSlowFrames:currentSlowFrames];
|
|
}
|
|
self.previouslyVisibleViewControllers = nil;
|
|
});
|
|
}
|
|
|
|
- (void)appWillResignActiveNotification:(NSNotification *)notification {
|
|
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
|
|
// soon as we're notified of an event.
|
|
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
|
|
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
|
|
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
|
|
|
|
dispatch_group_async(self.screenTraceTrackerDispatchGroup, self.screenTraceTrackerSerialQueue, ^{
|
|
self.previouslyVisibleViewControllers = [NSPointerArray weakObjectsPointerArray];
|
|
[self.activeScreenTracesLock lock];
|
|
NSArray<FPRScreenTraceHolder *> *holders = [self.activeScreenTraces allValues];
|
|
for (FPRScreenTraceHolder *holder in holders) {
|
|
UIViewController *vc = holder.viewController;
|
|
if (vc) {
|
|
[self.previouslyVisibleViewControllers addPointer:(__bridge void *)(vc)];
|
|
}
|
|
}
|
|
[self.activeScreenTracesLock unlock];
|
|
|
|
for (id visibleViewController in self.previouslyVisibleViewControllers) {
|
|
[self stopScreenTraceForViewController:visibleViewController
|
|
currentTotalFrames:currentTotalFrames
|
|
currentFrozenFrames:currentFrozenFrames
|
|
currentSlowFrames:currentSlowFrames];
|
|
}
|
|
});
|
|
}
|
|
|
|
#if TARGET_OS_TV
|
|
/** Handles the UIScreenModeDidChangeNotification. Recomputes the cached slow budget when the screen
|
|
* mode changes on tvOS.
|
|
*
|
|
* @param notification The NSNotification object.
|
|
*/
|
|
- (void)screenModeDidChangeNotification:(NSNotification *)notification {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self updateCachedSlowBudget];
|
|
});
|
|
}
|
|
#endif
|
|
|
|
/** Updates the cached slow budget. On tvOS, recomputes from UIScreen.maximumFramesPerSecond.
|
|
* On iOS, maintains kFPRSlowFrameThreshold.
|
|
* This method must be called on the main thread - and is available on iOS only for verification.
|
|
*/
|
|
- (void)updateCachedSlowBudget {
|
|
NSAssert([NSThread isMainThread], @"updateCachedSlowBudget must be called on main thread");
|
|
#if TARGET_OS_TV
|
|
UIScreen *mainScreen = [UIScreen mainScreen];
|
|
NSInteger maxFPS = 0;
|
|
if (mainScreen) {
|
|
maxFPS = mainScreen.maximumFramesPerSecond;
|
|
}
|
|
if (maxFPS > 0) {
|
|
_cachedMaxFPS = maxFPS;
|
|
_cachedSlowBudget = 1.0 / (CFTimeInterval)maxFPS;
|
|
} else {
|
|
// Fallback to default FPS if maximumFramesPerSecond is unavailable or invalid.
|
|
_cachedMaxFPS = kFPRDefaultFPS;
|
|
_cachedSlowBudget = 1.0 / (CFTimeInterval)kFPRDefaultFPS;
|
|
}
|
|
#else
|
|
// TODO(b/545164487): Updating this is likely the first step for ProMotion devices.
|
|
// https://github.com/firebase/firebase-ios-sdk/issues/10220#issuecomment-1248632304
|
|
_cachedSlowBudget = kFPRSlowFrameThreshold;
|
|
#endif
|
|
}
|
|
|
|
#pragma mark - Frozen, slow and good frames
|
|
|
|
- (void)displayLinkStep {
|
|
static CFAbsoluteTime previousTimestamp = kFPRInvalidTime;
|
|
CFAbsoluteTime currentTimestamp = self.displayLink.timestamp;
|
|
// Use the cached slow budget where needed - currently only on tvOS.
|
|
RecordFrameType(currentTimestamp, previousTimestamp, &_slowFramesCount, &_frozenFramesCount,
|
|
&_totalFramesCount, _cachedSlowBudget);
|
|
previousTimestamp = currentTimestamp;
|
|
}
|
|
|
|
/** This function increments the relevant frame counters based on the current and previous
|
|
* timestamp provided by the displayLink.
|
|
*
|
|
* @param currentTimestamp The current timestamp of the displayLink.
|
|
* @param previousTimestamp The previous timestamp of the displayLink.
|
|
* @param slowFramesCounter The value of the slowFramesCount before this function was called.
|
|
* @param frozenFramesCounter The value of the frozenFramesCount before this function was called.
|
|
* @param totalFramesCounter The value of the totalFramesCount before this function was called.
|
|
* @param slowBudget The frame duration threshold above which a frame is considered slow.
|
|
*/
|
|
FOUNDATION_STATIC_INLINE
|
|
void RecordFrameType(CFAbsoluteTime currentTimestamp,
|
|
CFAbsoluteTime previousTimestamp,
|
|
atomic_int_fast64_t *slowFramesCounter,
|
|
atomic_int_fast64_t *frozenFramesCounter,
|
|
atomic_int_fast64_t *totalFramesCounter,
|
|
CFTimeInterval slowBudget) {
|
|
CFTimeInterval frameDuration = currentTimestamp - previousTimestamp;
|
|
if (previousTimestamp == kFPRInvalidTime) {
|
|
return;
|
|
}
|
|
#if TARGET_OS_TV
|
|
// On tvOS, use cached slowBudget with epsilon to avoid floating point comparison issues
|
|
// (e.g., 59.94 vs 60 Hz displays).
|
|
if (frameDuration > slowBudget + kFPRSlowFrameEpsilon) {
|
|
atomic_fetch_add_explicit(slowFramesCounter, 1, memory_order_relaxed);
|
|
}
|
|
#else
|
|
if (frameDuration > kFPRSlowFrameThreshold) {
|
|
atomic_fetch_add_explicit(slowFramesCounter, 1, memory_order_relaxed);
|
|
}
|
|
#endif
|
|
if (frameDuration > kFPRFrozenFrameThreshold) {
|
|
atomic_fetch_add_explicit(frozenFramesCounter, 1, memory_order_relaxed);
|
|
}
|
|
atomic_fetch_add_explicit(totalFramesCounter, 1, memory_order_relaxed);
|
|
}
|
|
|
|
#pragma mark - Helper methods
|
|
|
|
/** Starts a screen trace for the given UIViewController instance if it doesn't exist. This method
|
|
* does NOT ensure thread safety - the caller is responsible for making sure that this is invoked
|
|
* in a thread safe manner.
|
|
*
|
|
* @param viewController The UIViewController instance for which the trace is to be started.
|
|
* @param currentTotalFrames The value of the totalFramesCount before this method was called.
|
|
* @param currentFrozenFrames The value of the frozenFramesCount before this method was called.
|
|
* @param currentSlowFrames The value of the slowFramesCount before this method was called.
|
|
*/
|
|
- (void)startScreenTraceForViewController:(UIViewController *)viewController
|
|
currentTotalFrames:(int64_t)currentTotalFrames
|
|
currentFrozenFrames:(int64_t)currentFrozenFrames
|
|
currentSlowFrames:(int64_t)currentSlowFrames {
|
|
if (![self shouldCreateScreenTraceForViewController:viewController]) {
|
|
return;
|
|
}
|
|
|
|
[self.activeScreenTracesLock lock];
|
|
[self cleanupStaleTraces];
|
|
|
|
NSValue *key = [NSValue valueWithNonretainedObject:viewController];
|
|
FPRScreenTraceHolder *holder = [self.activeScreenTraces objectForKey:key];
|
|
if (holder && holder.viewController != viewController) {
|
|
// Stale entry due to pointer reuse. Remove it.
|
|
[self.activeScreenTraces removeObjectForKey:key];
|
|
holder = nil;
|
|
}
|
|
|
|
if (!holder) {
|
|
NSString *traceName = FPRScreenTraceNameForViewController(viewController);
|
|
FIRTrace *newTrace = [[FIRTrace alloc] initInternalTraceWithName:traceName];
|
|
[newTrace start];
|
|
[newTrace setIntValue:currentTotalFrames forMetric:kFPRTotalFramesCounterName];
|
|
[newTrace setIntValue:currentFrozenFrames forMetric:kFPRFrozenFrameCounterName];
|
|
[newTrace setIntValue:currentSlowFrames forMetric:kFPRSlowFrameCounterName];
|
|
|
|
holder = [[FPRScreenTraceHolder alloc] init];
|
|
holder.viewController = viewController;
|
|
holder.trace = newTrace;
|
|
|
|
[self.activeScreenTraces setObject:holder forKey:key];
|
|
}
|
|
[self.activeScreenTracesLock unlock];
|
|
}
|
|
|
|
/** Stops a screen trace for the given UIViewController instance if it exist. This method does NOT
|
|
* ensure thread safety - the caller is responsible for making sure that this is invoked in a
|
|
* thread safe manner.
|
|
*
|
|
* @param viewController The UIViewController instance for which the trace is to be stopped.
|
|
* @param currentTotalFrames The value of the totalFramesCount before this method was called.
|
|
* @param currentFrozenFrames The value of the frozenFramesCount before this method was called.
|
|
* @param currentSlowFrames The value of the slowFramesCount before this method was called.
|
|
*/
|
|
- (void)stopScreenTraceForViewController:(UIViewController *)viewController
|
|
currentTotalFrames:(int64_t)currentTotalFrames
|
|
currentFrozenFrames:(int64_t)currentFrozenFrames
|
|
currentSlowFrames:(int64_t)currentSlowFrames {
|
|
if (viewController == nil) {
|
|
return;
|
|
}
|
|
NSValue *key = [NSValue valueWithNonretainedObject:viewController];
|
|
|
|
[self.activeScreenTracesLock lock];
|
|
FPRScreenTraceHolder *holder = [self.activeScreenTraces objectForKey:key];
|
|
if (holder) {
|
|
[self.activeScreenTraces removeObjectForKey:key];
|
|
if (holder.viewController != viewController) {
|
|
// Stale entry due to pointer reuse.
|
|
holder = nil;
|
|
}
|
|
}
|
|
[self.activeScreenTracesLock unlock];
|
|
|
|
if (!holder) {
|
|
return;
|
|
}
|
|
|
|
FIRTrace *previousScreenTrace = holder.trace;
|
|
|
|
// Get a diff between the counters now and what they were at trace start.
|
|
int64_t actualTotalFrames =
|
|
currentTotalFrames - [previousScreenTrace valueForIntMetric:kFPRTotalFramesCounterName];
|
|
int64_t actualFrozenFrames =
|
|
currentFrozenFrames - [previousScreenTrace valueForIntMetric:kFPRFrozenFrameCounterName];
|
|
int64_t actualSlowFrames =
|
|
currentSlowFrames - [previousScreenTrace valueForIntMetric:kFPRSlowFrameCounterName];
|
|
|
|
// Update the values in the trace.
|
|
if (actualTotalFrames != 0) {
|
|
[previousScreenTrace setIntValue:actualTotalFrames forMetric:kFPRTotalFramesCounterName];
|
|
} else {
|
|
[previousScreenTrace deleteMetric:kFPRTotalFramesCounterName];
|
|
}
|
|
|
|
if (actualFrozenFrames != 0) {
|
|
[previousScreenTrace setIntValue:actualFrozenFrames forMetric:kFPRFrozenFrameCounterName];
|
|
} else {
|
|
[previousScreenTrace deleteMetric:kFPRFrozenFrameCounterName];
|
|
}
|
|
|
|
if (actualSlowFrames != 0) {
|
|
[previousScreenTrace setIntValue:actualSlowFrames forMetric:kFPRSlowFrameCounterName];
|
|
} else {
|
|
[previousScreenTrace deleteMetric:kFPRSlowFrameCounterName];
|
|
}
|
|
|
|
if (previousScreenTrace.numberOfCounters > 0) {
|
|
[previousScreenTrace stop];
|
|
} else {
|
|
// The trace did not collect any data. Don't log it.
|
|
[previousScreenTrace cancel];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Filtering for screen traces
|
|
|
|
/** Determines whether to create a screen trace for the given UIViewController instance.
|
|
*
|
|
* @param viewController The UIViewController instance.
|
|
* @return YES if a screen trace should be created for the given UIViewController instance,
|
|
NO otherwise.
|
|
*/
|
|
- (BOOL)shouldCreateScreenTraceForViewController:(UIViewController *)viewController {
|
|
if (viewController == nil) {
|
|
return NO;
|
|
}
|
|
|
|
// Ignore non-main bundle view controllers whose class or superclass is an internal iOS view
|
|
// controller. This is borrowed from the logic for tracking screens in Firebase Analytics.
|
|
NSBundle *bundle = [NSBundle bundleForClass:[viewController class]];
|
|
if (bundle != [NSBundle mainBundle]) {
|
|
NSString *className = FPRUnprefixedClassName([viewController class]);
|
|
if ([className hasPrefix:@"_"]) {
|
|
return NO;
|
|
}
|
|
NSString *superClassName = FPRUnprefixedClassName([viewController superclass]);
|
|
if ([superClassName hasPrefix:@"_"]) {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
// We are not creating screen traces for these view controllers because they're container view
|
|
// controllers. They always have a child view controller which will provide better context for a
|
|
// screen trace. We are however capturing traces if a developer subclasses these as there may be
|
|
// some context. Special case: We are not capturing screen traces for any input view
|
|
// controllers.
|
|
return !([viewController isMemberOfClass:[UINavigationController class]] ||
|
|
[viewController isMemberOfClass:[UITabBarController class]] ||
|
|
[viewController isMemberOfClass:[UISplitViewController class]] ||
|
|
[viewController isMemberOfClass:[UIPageViewController class]] ||
|
|
[viewController isKindOfClass:[UIInputViewController class]]);
|
|
}
|
|
|
|
- (void)cleanupStaleTraces {
|
|
[self.activeScreenTracesLock lock];
|
|
NSMutableArray *keysToRemove = [NSMutableArray array];
|
|
[self.activeScreenTraces
|
|
enumerateKeysAndObjectsUsingBlock:^(NSValue *key, FPRScreenTraceHolder *holder, BOOL *stop) {
|
|
if (holder.viewController == nil) {
|
|
[keysToRemove addObject:key];
|
|
}
|
|
}];
|
|
[self.activeScreenTraces removeObjectsForKeys:keysToRemove];
|
|
[self.activeScreenTracesLock unlock];
|
|
}
|
|
|
|
#pragma mark - Screen Traces swizzling hooks
|
|
|
|
- (void)viewControllerDidAppear:(UIViewController *)viewController {
|
|
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
|
|
// soon as we're notified of an event.
|
|
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
|
|
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
|
|
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
|
|
|
|
dispatch_sync(self.screenTraceTrackerSerialQueue, ^{
|
|
[self startScreenTraceForViewController:viewController
|
|
currentTotalFrames:currentTotalFrames
|
|
currentFrozenFrames:currentFrozenFrames
|
|
currentSlowFrames:currentSlowFrames];
|
|
});
|
|
}
|
|
|
|
- (void)viewControllerDidDisappear:(id)viewController {
|
|
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
|
|
// soon as we're notified of an event.
|
|
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
|
|
int64_t currentFrozenFrames = atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
|
|
int64_t currentSlowFrames = atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
|
|
|
|
dispatch_sync(self.screenTraceTrackerSerialQueue, ^{
|
|
[self stopScreenTraceForViewController:viewController
|
|
currentTotalFrames:currentTotalFrames
|
|
currentFrozenFrames:currentFrozenFrames
|
|
currentSlowFrames:currentSlowFrames];
|
|
});
|
|
}
|
|
|
|
#pragma mark - Test Helper Methods
|
|
|
|
- (int_fast64_t)totalFramesCount {
|
|
return atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
|
|
}
|
|
|
|
- (void)setTotalFramesCount:(int_fast64_t)count {
|
|
atomic_store_explicit(&_totalFramesCount, count, memory_order_relaxed);
|
|
}
|
|
|
|
- (int_fast64_t)slowFramesCount {
|
|
return atomic_load_explicit(&_slowFramesCount, memory_order_relaxed);
|
|
}
|
|
|
|
- (void)setSlowFramesCount:(int_fast64_t)count {
|
|
atomic_store_explicit(&_slowFramesCount, count, memory_order_relaxed);
|
|
}
|
|
|
|
- (int_fast64_t)frozenFramesCount {
|
|
return atomic_load_explicit(&_frozenFramesCount, memory_order_relaxed);
|
|
}
|
|
|
|
- (void)setFrozenFramesCount:(int_fast64_t)count {
|
|
atomic_store_explicit(&_frozenFramesCount, count, memory_order_relaxed);
|
|
}
|
|
|
|
@end
|