Skip to content

Instantly share code, notes, and snippets.

@kocienda
Created September 7, 2022 02:45
Show Gist options
  • Save kocienda/8380ec43090916a4cbca1792123f9486 to your computer and use it in GitHub Desktop.
Save kocienda/8380ec43090916a4cbca1792123f9486 to your computer and use it in GitHub Desktop.
A ticker class that uses CADisplayLink
//
// UPTicker.h
// Copyright © 2020-2022 Ken Kocienda. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
@class UPTickingAnimator;
@protocol UPTicking;
extern CFTimeInterval UPTickerInterval;
@interface UPTicker : NSObject
+ (UPTicker *)instance;
- (instancetype)init NS_UNAVAILABLE;
- (void)addTicking:(NSObject<UPTicking> *)ticking;
- (void)removeTicking:(NSObject<UPTicking> *)ticking;
- (void)removeAllTickings;
- (void)start;
- (void)stop;
@end
@protocol UPTicking <NSObject>
@property (nonatomic, readonly) uint32_t serialNumber;
- (void)tick:(CFTimeInterval)now;
@end
//
// UPTicker.m
// Copyright © 2020-2022 Ken Kocienda. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <UIKit/UIScreen.h>
#import "UPAssertions.h"
#import "UPMacros.h"
#import "UPTicker.h"
CFTimeInterval UPTickerInterval = 1.0 / 60.0;
@interface UPTicker ()
@property (nonatomic) NSMutableSet<NSObject<UPTicking> *> *tickings;
@property (nonatomic) NSMutableArray<NSObject<UPTicking> *> *iterationTickings;
@property (nonatomic) CADisplayLink *displayLink;
@end
@implementation UPTicker
+ (UPTicker *)instance
{
static dispatch_once_t onceToken;
static UPTicker *instance;
dispatch_once(&onceToken, ^{
instance = [[self alloc] _init];
});
return instance;
}
- (instancetype)_init
{
self = [super init];
self.tickings = [NSMutableSet set];
self.iterationTickings = [NSMutableArray array];
return self;
}
- (void)addTicking:(NSObject<UPTicking> *)ticking
{
[self.tickings addObject:ticking];
[self _startDisplayLinkIfNeeded];
}
- (void)removeTicking:(NSObject<UPTicking> *)ticking
{
[self.tickings removeObject:ticking];
[self _stopDisplayLinkIfNoTickings];
}
- (void)removeAllTickings
{
[self.tickings removeAllObjects];
[self stop];
}
- (void)start
{
[self _startDisplayLinkIfNeeded];
}
- (void)stop
{
[self.displayLink invalidate];
self.displayLink = nil;
}
static BOOL tickIntervalChecked = NO;
- (void)_tick:(CADisplayLink *)sender
{
if (UNLIKELY(!tickIntervalChecked)) {
tickIntervalChecked = YES;
UPTickerInterval = self.displayLink.duration;
}
CFTimeInterval now = CACurrentMediaTime();
[self.iterationTickings removeAllObjects];
for (NSObject<UPTicking> *ticking in self.tickings) {
[self.iterationTickings addObject:ticking];
}
if (self.iterationTickings.count > 1) {
[self.iterationTickings sortUsingComparator:
^NSComparisonResult(id obj1, id obj2) {
NSObject<UPTicking> *t1 = obj1;
NSObject<UPTicking> *t2 = obj2;
if (t1.serialNumber < t2.serialNumber) {
return NSOrderedAscending;
}
if (t1.serialNumber > t2.serialNumber) {
return NSOrderedDescending;
}
return NSOrderedSame;
}];
}
for (NSObject<UPTicking> *ticking in self.iterationTickings) {
[ticking tick:now];
}
}
- (void)_startDisplayLinkIfNeeded
{
if (self.tickings.count == 0) {
return;
}
if (!self.displayLink) {
tickIntervalChecked = NO;
self.displayLink = [[UIScreen mainScreen] displayLinkWithTarget:self selector:@selector(_tick:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
- (void)_stopDisplayLinkIfNoTickings
{
if (self.tickings.count == 0) {
[self stop];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment