Last active
August 17, 2018 18:45
-
-
Save drumnkyle/89180f310d705df75647e45dc5f8fd59 to your computer and use it in GitHub Desktop.
KSScrollPerformanceDetector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
/** | |
These methods will not be called on the main thread. So, | |
if you will be doing anything with UIKit, ensure you dispatch back | |
to the main thread. | |
*/ | |
@protocol KSScrollPerformanceDetectorDelegate<NSObject> | |
@optional | |
- (void)framesDropped:(NSInteger)framesDroppedCount cumulativeFramesDropped:(NSInteger)cumulativeFramesDropped cumulativeFrameDropEvents:(NSInteger)cumulativeFrameDropEvents; | |
@end | |
@interface KSScrollPerformanceDetector : NSObject | |
@property(nonatomic, assign, readonly) NSInteger currentFrameDropCount; | |
@property(nonatomic, assign, readonly) NSInteger currentFrameDropEventCount; | |
@property(nonatomic, weak, nullable) id<KSScrollPerformanceDetectorDelegate> delegate; | |
- (nonnull instancetype)init NS_DESIGNATED_INITIALIZER; | |
- (void)resume; | |
- (void)pause; | |
- (void)clearFrameDropCount; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "KSScrollPerformanceDetector.h" | |
#import <UIKit/UIKit.h> | |
@interface KSScrollPerformanceDetector() | |
@property(nonatomic, strong, nonnull) CADisplayLink *displayLink; | |
@property(nonatomic, assign, readwrite) CFTimeInterval lastTimestamp; | |
@property(nonatomic, assign, readwrite) NSInteger currentFrameDropCount; | |
@property(nonatomic, assign, readwrite) NSInteger currentFrameDropEventCount; | |
@property(nonatomic, strong, nonnull) dispatch_queue_t workerQueue; | |
- (void)displayLinkTriggered:(CADisplayLink *)sender; | |
- (void)calculateFrameDropForNumberOfFrames:(NSInteger)numberOfFrames; | |
- (void)registerLifecyleNotifications; | |
@end | |
@implementation KSScrollPerformanceDetector | |
#pragma mark - Initializers | |
- (instancetype)init { | |
self = [super init]; | |
[self registerLifecyleNotifications]; | |
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTriggered:)]; | |
_displayLink.paused = YES; | |
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; | |
_lastTimestamp = 0; | |
dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH); | |
_workerQueue = dispatch_queue_create("com.frame-rate-reporter.worker", attributes); | |
return self; | |
} | |
#pragma mark - Public Methods | |
- (void)clearFrameDropCount { | |
__weak typeof(self) weakSelf = self; | |
dispatch_async(self.workerQueue, ^{ | |
typeof(self) strongSelf = weakSelf; | |
if (!strongSelf) { | |
return; | |
} | |
self->_currentFrameDropCount = 0; | |
self->_currentFrameDropEventCount = 0; | |
}); | |
} | |
- (void)resume { | |
self.displayLink.paused = NO; | |
} | |
- (void)pause { | |
self.displayLink.paused = YES; | |
} | |
#pragma mark - Private Methods | |
#pragma mark Lifecyle | |
- (void)registerLifecyleNotifications { | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(notifyActivate:) | |
name:UIApplicationDidBecomeActiveNotification | |
object:nil]; | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(notifyDeactivate:) | |
name:UIApplicationWillResignActiveNotification | |
object:nil]; | |
} | |
- (void)notifyActivate:(NSNotification *)notification { | |
self.displayLink.paused = NO; | |
self.lastTimestamp = 0; | |
} | |
- (void)notifyDeactivate:(NSNotification *)notification { | |
self.displayLink.paused = YES; | |
} | |
- (void)dealloc { | |
[[NSNotificationCenter defaultCenter] removeObserver:self]; | |
self.displayLink.paused = YES; | |
[self.displayLink invalidate]; | |
} | |
#pragma mark Calculations | |
- (void)displayLinkTriggered:(CADisplayLink *)sender { | |
dispatch_async(self.workerQueue, ^{ | |
if (self.lastTimestamp == 0) { | |
self.lastTimestamp = sender.timestamp; | |
return; | |
} | |
CFTimeInterval duration = sender.duration; | |
if (duration == 0) { | |
return; | |
} | |
double numberOfFramesDouble = round((sender.timestamp - self.lastTimestamp) / duration); | |
NSAssert(numberOfFramesDouble <= NSIntegerMax && numberOfFramesDouble >= NSIntegerMin, @"fps double value out of range of NSInteger"); | |
NSInteger numberOfFrames = numberOfFramesDouble; | |
self.lastTimestamp = sender.timestamp; | |
[self calculateFrameDropForNumberOfFrames:numberOfFrames]; | |
}); | |
} | |
- (void)calculateFrameDropForNumberOfFrames:(NSInteger)numberOfFrames { | |
NSInteger droppedFrameCount = numberOfFrames - 1 > 0 ? numberOfFrames : 0; | |
self.currentFrameDropCount += droppedFrameCount; | |
if (droppedFrameCount > 0) { | |
self.currentFrameDropEventCount += 1; | |
} | |
if (droppedFrameCount > 0 && | |
self.listener && | |
[self.delegate respondsToSelector:@selector(framesDropped:cumulativeFramesDropped:cumulativeFrameDropEvents:)]) { | |
[self.delegate framesDropped:droppedFrameCount | |
cumulativeFramesDropped:self.currentFrameDropCount | |
cumulativeFrameDropEvents:self.currentFrameDropEventCount]; | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment