mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
Merge pull request #3258 from enquora/modernize-CPTimer
Refactor CPTimer: Remove CPP dependencies and mark legacy anti-patterns
This commit is contained in:
+163
-155
@@ -25,285 +25,293 @@
|
|||||||
@import "CPObject.j"
|
@import "CPObject.j"
|
||||||
@import "CPRunLoop.j"
|
@import "CPRunLoop.j"
|
||||||
|
|
||||||
#define CPTimerDefaultTimeInterval 0.1
|
// FIXME: Expose CPTimerDefaultTimeInterval via public API or eliminate the fallback behaviour.
|
||||||
|
const CPTimerDefaultTimeInterval = 0.1;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@class CPTimer
|
@class CPTimer
|
||||||
@ingroup foundation
|
@ingroup foundation
|
||||||
|
|
||||||
@brief A timer object that can send a message after the given time interval.
|
@brief A timer object that can send a message after the given time interval.
|
||||||
*/
|
*/
|
||||||
@implementation CPTimer : CPObject
|
@implementation CPTimer : CPObject
|
||||||
{
|
{
|
||||||
CPTimeInterval _timeInterval;
|
CPTimeInterval _timeInterval;
|
||||||
CPInvocation _invocation;
|
CPInvocation _invocation;
|
||||||
Function _callback;
|
Function _callback;
|
||||||
|
|
||||||
BOOL _repeats;
|
BOOL _repeats;
|
||||||
BOOL _isValid;
|
BOOL _isValid;
|
||||||
CPDate _fireDate;
|
CPDate _fireDate;
|
||||||
id _userInfo;
|
id _userInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
|
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
|
||||||
*/
|
*/
|
||||||
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
|
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat];
|
const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat];
|
||||||
|
|
||||||
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
|
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
|
||||||
|
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
|
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
|
||||||
*/
|
*/
|
||||||
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
|
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat];
|
const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat];
|
||||||
|
|
||||||
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
|
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
|
||||||
|
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
|
Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode.
|
||||||
*/
|
*/
|
||||||
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
|
+ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat];
|
const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat];
|
||||||
|
|
||||||
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
|
[[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode];
|
||||||
|
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
|
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
|
||||||
*/
|
*/
|
||||||
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
|
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat];
|
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
|
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
|
||||||
*/
|
*/
|
||||||
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
|
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat];
|
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
|
Returns a new CPTimer that, when added to a run loop, will fire after seconds.
|
||||||
*/
|
*/
|
||||||
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
|
+ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat];
|
return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
|
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
|
||||||
*/
|
*/
|
||||||
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
|
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
|
|
||||||
if (self)
|
if (self)
|
||||||
{
|
{
|
||||||
_timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds;
|
_timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds;
|
||||||
_invocation = anInvocation;
|
_invocation = anInvocation;
|
||||||
_repeats = shouldRepeat;
|
_repeats = shouldRepeat;
|
||||||
_isValid = YES;
|
_isValid = YES;
|
||||||
_fireDate = aDate;
|
_fireDate = aDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
|
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
|
||||||
*/
|
*/
|
||||||
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
|
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
var invocation = [CPInvocation invocationWithMethodSignature:1];
|
const invocation = [CPInvocation invocationWithMethodSignature:1];
|
||||||
|
|
||||||
[invocation setTarget:aTarget];
|
[invocation setTarget:aTarget];
|
||||||
[invocation setSelector:aSelector];
|
[invocation setSelector:aSelector];
|
||||||
[invocation setArgument:self atIndex:2];
|
[invocation setArgument:self atIndex:2];
|
||||||
|
|
||||||
self = [self initWithFireDate:aDate interval:seconds invocation:invocation repeats:shouldRepeat];
|
self = [self initWithFireDate:aDate interval:seconds invocation:invocation repeats:shouldRepeat];
|
||||||
|
|
||||||
if (self)
|
if (self)
|
||||||
_userInfo = userInfo;
|
_userInfo = userInfo;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
|
Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that.
|
||||||
*/
|
*/
|
||||||
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
|
- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
|
|
||||||
if (self)
|
if (self)
|
||||||
{
|
{
|
||||||
_timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds;
|
_timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds;
|
||||||
_callback = aFunction;
|
_callback = aFunction;
|
||||||
_repeats = shouldRepeat;
|
_repeats = shouldRepeat;
|
||||||
_isValid = YES;
|
_isValid = YES;
|
||||||
_fireDate = aDate;
|
_fireDate = aDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns the receiver’s time interval.
|
Returns the receiver’s time interval.
|
||||||
*/
|
*/
|
||||||
- (CPTimeInterval)timeInterval
|
- (CPTimeInterval)timeInterval
|
||||||
{
|
{
|
||||||
return _timeInterval;
|
return _timeInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns the date at which the receiver will fire.
|
Returns the date at which the receiver will fire.
|
||||||
*/
|
*/
|
||||||
- (CPDate)fireDate
|
- (CPDate)fireDate
|
||||||
{
|
{
|
||||||
return _fireDate;
|
return _fireDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Resets the receiver to fire next at a given date.
|
Resets the receiver to fire next at a given date.
|
||||||
*/
|
*/
|
||||||
- (void)setFireDate:(CPDate)aDate
|
- (void)setFireDate:(CPDate)aDate
|
||||||
{
|
{
|
||||||
_fireDate = aDate;
|
_fireDate = aDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Causes the receiver’s message to be sent to its target.
|
Causes the receiver’s message to be sent to its target.
|
||||||
*/
|
*/
|
||||||
- (void)fire
|
- (void)fire
|
||||||
{
|
{
|
||||||
if (!_isValid)
|
if (!_isValid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_callback)
|
if (_callback)
|
||||||
_callback();
|
_callback();
|
||||||
else
|
else
|
||||||
[_invocation invoke];
|
[_invocation invoke];
|
||||||
|
|
||||||
if (!_isValid)
|
if (!_isValid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_repeats)
|
if (_repeats)
|
||||||
_fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval];
|
_fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval];
|
||||||
|
else
|
||||||
else
|
[self invalidate];
|
||||||
[self invalidate];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns a Boolean value that indicates whether the receiver is currently valid.
|
Returns a Boolean value that indicates whether the receiver is currently valid.
|
||||||
*/
|
*/
|
||||||
- (BOOL)isValid
|
- (BOOL)isValid
|
||||||
{
|
{
|
||||||
return _isValid;
|
return _isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Stops the receiver from ever firing again and requests its removal from its CPRunLoop object.
|
Stops the receiver from ever firing again and requests its removal from its CPRunLoop object.
|
||||||
*/
|
*/
|
||||||
- (void)invalidate
|
- (void)invalidate
|
||||||
{
|
{
|
||||||
_isValid = NO;
|
_isValid = NO;
|
||||||
_userInfo = nil;
|
_userInfo = nil;
|
||||||
_invocation = nil;
|
_invocation = nil;
|
||||||
_callback = nil;
|
_callback = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Returns the receiver's userInfo object.
|
Returns the receiver's userInfo object.
|
||||||
*/
|
*/
|
||||||
- (id)userInfo
|
- (id)userInfo
|
||||||
{
|
{
|
||||||
return _userInfo;
|
return _userInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
var CPTimersTimeoutID = 1000,
|
// FIXME: Anti-pattern: Global DOM Override. This section invasively overrides global DOM timing
|
||||||
CPTimersForTimeoutIDs = {};
|
// functions (window.setTimeout, setInterval) to force external execution through CPRunLoop.
|
||||||
|
// This deep coupling creates unpredictable side effects for third-party libraries and should
|
||||||
|
// be replaced with a non-invasive run loop integration strategy.
|
||||||
|
let CPTimersTimeoutID = 1000;
|
||||||
|
|
||||||
var _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs)
|
// FIXME: Anti-pattern: Manual Global Tracking. Tracking bridged DOM timers in a global map like
|
||||||
|
// this is brittle and prone to memory leaks in long-running processes.
|
||||||
|
const CPTimersForTimeoutIDs = {};
|
||||||
|
|
||||||
|
const _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs)
|
||||||
{
|
{
|
||||||
var timeoutID = CPTimersTimeoutID++,
|
const timeoutID = CPTimersTimeoutID++;
|
||||||
theFunction = nil;
|
let theFunction = nil;
|
||||||
|
|
||||||
if (typeof codeOrFunction === "string")
|
if (typeof codeOrFunction === "string")
|
||||||
{
|
{
|
||||||
theFunction = function()
|
// FIXME: Anti-pattern: Dynamic Evaluation. Evaluating string payloads via `new Function`
|
||||||
{
|
// is a strict Content Security Policy (CSP) violation.
|
||||||
new Function(codeOrFunction)();
|
theFunction = function()
|
||||||
|
{
|
||||||
|
new Function(codeOrFunction)();
|
||||||
|
|
||||||
if (!shouldRepeat)
|
if (!shouldRepeat)
|
||||||
CPTimersForTimeoutIDs[timeoutID] = nil;
|
delete CPTimersForTimeoutIDs[timeoutID];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!functionArgs)
|
if (!functionArgs)
|
||||||
functionArgs = [];
|
functionArgs = [];
|
||||||
|
|
||||||
theFunction = function()
|
theFunction = function()
|
||||||
{
|
{
|
||||||
codeOrFunction.apply(window, functionArgs);
|
codeOrFunction.apply(window, functionArgs);
|
||||||
|
|
||||||
if (!shouldRepeat)
|
if (!shouldRepeat)
|
||||||
CPTimersForTimeoutIDs[timeoutID] = nil;
|
delete CPTimersForTimeoutIDs[timeoutID];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A call such as setTimeout(f) is technically invalid but browsers seem to treat it as setTimeout(f, 0), so so will we.
|
// A call such as setTimeout(f) is technically invalid but browsers seem to treat it as setTimeout(f, 0), so so will we.
|
||||||
aDelay = aDelay | 0.0;
|
aDelay = aDelay | 0.0;
|
||||||
|
|
||||||
CPTimersForTimeoutIDs[timeoutID] = [CPTimer scheduledTimerWithTimeInterval:aDelay / 1000 callback:theFunction repeats:shouldRepeat];
|
CPTimersForTimeoutIDs[timeoutID] = [CPTimer scheduledTimerWithTimeInterval:aDelay / 1000 callback:theFunction repeats:shouldRepeat];
|
||||||
|
|
||||||
return timeoutID;
|
return timeoutID;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Avoid "TypeError: Result of expression 'window' [undefined] is not an object" when running unit tests.
|
// Avoid "TypeError: Result of expression 'window' [undefined] is not an object" when running unit tests.
|
||||||
// We can't use a regular PLATFORM(DOM) check because that platform constant is not defined in Foundation.
|
|
||||||
if (typeof(window) !== 'undefined')
|
if (typeof(window) !== 'undefined')
|
||||||
{
|
{
|
||||||
window.setTimeout = function(codeOrFunction, aDelay)
|
window.setTimeout = function(codeOrFunction, aDelay)
|
||||||
{
|
{
|
||||||
return _CPTimerBridgeTimer(codeOrFunction, aDelay, NO, Array.prototype.slice.apply(arguments, [2]));
|
return _CPTimerBridgeTimer(codeOrFunction, aDelay, NO, Array.prototype.slice.apply(arguments, [2]));
|
||||||
};
|
};
|
||||||
|
|
||||||
window.clearTimeout = function(aTimeoutID)
|
window.clearTimeout = function(aTimeoutID)
|
||||||
{
|
{
|
||||||
var timer = CPTimersForTimeoutIDs[aTimeoutID];
|
const timer = CPTimersForTimeoutIDs[aTimeoutID];
|
||||||
|
|
||||||
if (timer)
|
if (timer)
|
||||||
[timer invalidate];
|
[timer invalidate];
|
||||||
|
|
||||||
CPTimersForTimeoutIDs[aTimeoutID] = nil;
|
delete CPTimersForTimeoutIDs[aTimeoutID];
|
||||||
};
|
};
|
||||||
|
|
||||||
window.setInterval = function(codeOrFunction, aDelay, functionArgs)
|
window.setInterval = function(codeOrFunction, aDelay, functionArgs)
|
||||||
{
|
{
|
||||||
return _CPTimerBridgeTimer(codeOrFunction, aDelay, YES, Array.prototype.slice.apply(arguments, [2]));
|
return _CPTimerBridgeTimer(codeOrFunction, aDelay, YES, Array.prototype.slice.apply(arguments, [2]));
|
||||||
};
|
};
|
||||||
|
|
||||||
window.clearInterval = function(aTimeoutID)
|
window.clearInterval = function(aTimeoutID)
|
||||||
{
|
{
|
||||||
window.clearTimeout(aTimeoutID);
|
window.clearTimeout(aTimeoutID);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user