diff --git a/ChangeLog b/ChangeLog index 0a58eb002..59666096b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,7 +5,11 @@ method to only fire one timer (excluding the private housekeeping one). Changing to only fire one timer actually allows the code to be simpler so it's a good update in its own right, not just a OSX compatibility - tweak. + tweak. I also made incrementing repeated timers a bit more robust, + checking that they provide a valid increment time interval and + removing them from the loop if they don't. + * Source/NSTimer.m: slightly tidy/clarify initialisation code and + documentation. 2009-09-08 Richard Frith-Macdonald diff --git a/Source/NSRunLoop.m b/Source/NSRunLoop.m index 64c10d0bb..2669bea3e 100644 --- a/Source/NSRunLoop.m +++ b/Source/NSRunLoop.m @@ -62,6 +62,7 @@ #ifdef HAVE_UNISTD_H #include #endif +#include #include #include #include /* for memset() */ @@ -813,6 +814,20 @@ static inline BOOL timerInvalidated(NSTimer *t) GSIArray timers; unsigned i; + if ([timer isKindOfClass: [NSTimer class]] == NO + || [timer isProxy] == YES) + { + [NSException raise: NSInvalidArgumentException + format: @"[%@-%@] not a valid timer", + NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + } + if ([mode isKindOfClass: [NSString class]] == NO) + { + [NSException raise: NSInvalidArgumentException + format: @"[%@-%@] not a valid mode", + NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + } + NSDebugMLLog(@"NSRunLoop", @"add timer for %f in %@", [[timer fireDate] timeIntervalSinceReferenceDate], mode); @@ -875,10 +890,38 @@ updateTimer(NSTimer *t, NSDate *d, NSTimeInterval now) NSTimeInterval ti = [d timeIntervalSinceReferenceDate]; NSTimeInterval increment = [t timeInterval]; - ti += increment; - while (ti < now) + if (increment <= 0.0) { - ti += increment; + /* Should never get here ... unless a subclass is returning + * a bad interval ... we return NO so that the timer gets + * removed from the loop. + */ + NSLog(@"WARNING timer %@ had bad interval ... removed", t); + return NO; + } + + ti += increment; // Hopefully a single increment will do. + + if (ti < now) + { + NSTimeInterval add; + + /* Just incrementing the date was insufficieint to bring it to + * the current time, so we must have missed one or more fire + * opportunities, or the fire date has been set on the timer. + * If a fire date long ago has been set and the increment value + * is really small, we might need to increment very many times + * to get the new fire date. To avoid looping for ages, we + * calculate the number of increments needed and do them in one + * go. + */ + add = floor((now - ti) / increment); + ti += (increment * add); + if (ti < now) + { + add++; + ti += increment; + } } d = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate: ti]; [t setFireDate: d]; diff --git a/Source/NSTimer.m b/Source/NSTimer.m index da03012b9..6165b65e9 100644 --- a/Source/NSTimer.m +++ b/Source/NSTimer.m @@ -69,22 +69,21 @@ static Class NSDate_class; return nil; } -/** - * +/** * Initialise the receive, a newly allocated NSTimer object.
- * The fd argument specifies an initial fire date ... if it is not - * supplied (a nil object) then the ti argument is used to create - * a start date relative to the current time.
* The ti argument specifies the time (in seconds) between the firing. * If it is less than or equal to 0.0 then a small interval is chosen * automatically.
+ * The fd argument specifies an initial fire date copied by the timer... + * if it is not supplied (a nil object) then the ti argument is used to + * create a start date relative to the current time.
* The f argument specifies whether the timer will fire repeatedly * or just once.
* If the selector argument is zero, then then object is an invocation * to be used when the timer fires. otherwise, the object is sent the * message specified by the selector and with the timer as an argument.
- * The fd, object and info arguments will be retained until the timer is - * invalidated.
+ * The object and info arguments will be retained until the timer is + * invalidated. */ - (id) initWithFireDate: (NSDate*)fd interval: (NSTimeInterval)ti @@ -93,26 +92,30 @@ static Class NSDate_class; userInfo: (id)info repeats: (BOOL)f { - if (ti <= 0) + if (ti <= 0.0) { ti = 0.0001; } - _interval = ti; if (fd == nil) { _date = [[NSDate_class allocWithZone: NSDefaultMallocZone()] - initWithTimeIntervalSinceNow: _interval]; + initWithTimeIntervalSinceNow: ti]; } else { - _date = [fd copy]; + _date = [fd copyWithZone: NSDefaultMallocZone()]; } _target = RETAIN(object); _selector = selector; _info = RETAIN(info); - _repeats = f; - if (_repeats == NO) + if (f == YES) { + _repeats = YES; + _interval = ti; + } + else + { + _repeats = NO; _interval = 0.0; } return self; @@ -266,7 +269,6 @@ static Class NSDate_class; - (void) invalidate { /* OPENSTEP allows this method to be called multiple times. */ - //NSAssert(_invalidated == NO, NSInternalInconsistencyException); _invalidated = YES; if (_target != nil) {