Understanding iOS Timers: NSTimer, CADisplayLink, and GCD Dispatch Sources
This article explains the three main iOS timer mechanisms—NSTimer, CADisplayLink, and GCD dispatch sources—detailing their creation methods, differences, common pitfalls such as RunLoop mode and thread issues, and practical solutions for accurate scheduling.
In iOS development, timers are essential for tasks such as data aggregation and countdowns, and three main timer APIs are commonly used: NSTimer , CADisplayLink , and GCD‑based dispatch sources . All of them rely on the RunLoop mechanism, but GCD timers achieve higher precision through their own scheduling system.
NSTimer
NSTimer is the most basic iOS timer. It is accurate under normal RunLoop conditions but can be delayed when the RunLoop is busy or when the timer is added to a specific RunLoopMode.
Initialization methods
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;Two calling styles are supported: invocation and selector . The selector style is more convenient in most cases.
Invocation example
- (void)logicMethod{
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:@selector(Timefired:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = @selector(Timefired:);
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 invocation:invocation repeats:YES];
}
- (void)Timefired:(NSTimer *)timer {
NSLog(@"timer called");
}Selector example
- (void)logicMethod {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:60.0 target:self selector:@selector(Timefired:) userInfo:nil repeats:YES];
}
- (void)Timefired:(NSTimer *)timer {
NSLog(@"timer called");
}ScheduledTimer vs timerWith
scheduledTimerWith… creates the timer and automatically adds it to the current RunLoop in NSDefaultRunLoopMode . In contrast, timerWith… only creates the timer; you must manually add it to a RunLoop:
You must add the new timer to a run loop, using addTimer:forMode:.
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(Timered) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];Releasing a timer
[timer invalidate];
timer = nil;When a timer is created, its target is strongly retained, which can cause retain cycles if the target also retains the timer. Breaking this cycle (e.g., using a weak‑reference proxy) is essential to avoid memory leaks.
Common NSTimer pitfalls
Delay caused by a busy RunLoop or mismatched RunLoopMode.
Timers created on background threads need an active RunLoop.
Retain cycles between the timer and its target.
Solutions include adding the timer to NSRunLoopCommonModes , using GCD timers, or moving the timer to a dedicated thread with its own RunLoop.
CADisplayLink
CADisplayLink synchronizes callbacks with the screen refresh rate (typically 60 Hz). It is highly precise but still subject to RunLoop mode and heavy‑task delays.
Creation
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];Stopping
[self.displayLink invalidate];
self.displayLink = nil;Key properties:
frameInterval – number of frames between callbacks (default 1).
duration – read‑only time between frames; the actual interval equals duration × frameInterval .
Because CADisplayLink runs on the RunLoop, adding it to NSRunLoopCommonModes ensures it fires during UI tracking (e.g., scrolling).
GCD Dispatch Source Timer
GCD timers are implemented with dispatch_source_t and provide the most accurate scheduling. They run on any dispatch queue, keeping the UI thread free.
Declaration
@property (nonatomic, strong) dispatch_source_t timer;Creation
// Delay and interval in seconds
NSTimeInterval delayTime = 3.0f;
NSTimeInterval timeInterval = 3.0f;
// Background queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Create timer source
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// Set start time
dispatch_time_t startDelayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC));
// Configure timer (leeway = 0.1 s)
dispatch_source_set_timer(_timer, startDelayTime, timeInterval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
// Event handler
dispatch_source_set_event_handler(_timer, ^{
// Execute event
});
// Start timer
dispatch_resume(_timer);Stopping
dispatch_source_cancel(_timer);To make the timer fire only once, cancel it inside the event handler:
dispatch_source_set_event_handler(_timer, ^{
// Execute event
dispatch_source_cancel(_timer);
});Important properties:
start – the initial fire time, set via dispatch_time (e.g., DISPATCH_TIME_NOW for immediate start).
interval – repeat interval, expressed in nanoseconds.
leeway – tolerance that lets the system coalesce timer events to save power; a larger leeway reduces CPU wake‑ups.
Using GCD timers avoids RunLoop‑related delays and keeps heavy work off the main thread, improving UI responsiveness.
Summary of Timing Issues and Remedies
The main reasons timers become inaccurate are a busy RunLoop and mismatched RunLoop modes. Recommended solutions are:
Minimize long‑running tasks on the RunLoop.
Use NSRunLoopCommonModes for timers that must fire during UI tracking.
Prefer GCD dispatch source timers for high‑precision needs.
When necessary, create a dedicated thread with its own RunLoop and add the timer there.
Properly managing timer lifecycle (invalidating and nil‑ing) and avoiding retain cycles (e.g., via weak‑reference proxies) prevents memory leaks.
Reference: iOS Development Timers.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.