From f4b08a9ce268ac9c9448bf5d52a80752dee164fc Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Tue, 5 Mar 2013 18:51:24 +0000 Subject: [PATCH] New: - CPObject performSelector:withObject:afterDelay:. Also - CPObject cancelPreviousPerformWithTarget: and other related methods. --- Foundation/CPDelayedPerform.j | 153 +++++++++++++++++++++++++++++++ Foundation/CPRunLoop.j | 10 +- Foundation/Foundation.j | 3 +- Tests/Foundation/CPRunLoopTest.j | 52 +++++++++++ 4 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 Foundation/CPDelayedPerform.j create mode 100644 Tests/Foundation/CPRunLoopTest.j diff --git a/Foundation/CPDelayedPerform.j b/Foundation/CPDelayedPerform.j new file mode 100644 index 000000000..170595284 --- /dev/null +++ b/Foundation/CPDelayedPerform.j @@ -0,0 +1,153 @@ +/* + * CPDelayedPerform.j + * Foundation + * + * Portions based on NSDelayedPerform.m (2013-03-03) in Cocotron (http://www.cocotron.org/) + * Copyright (c) 2006-2007 Christopher J. W. Lloyd + * + * Created by Alexander Ljungberg. + * Copyright 2013, SlevenBits Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "CPObject.j" +@import "CPRunLoop.j" +@import "CPString.j" + +@implementation CPDelayedPerform : CPObject +{ + id _object; + SEL _selector; + id _argument; +} + ++ (CPDelayedPerform)delayedPerformWithObject:anObject selector:(SEL)aSelector argument:anArgument +{ + return [[self alloc] initWithObject:anObject selector:aSelector argument:anArgument]; +} + +- (id)initWithObject:(id)anObject selector:(SEL)aSelector argument:(id)anArgument +{ + if (self = [super init]) + { + _object = anObject; + _selector = aSelector; + _argument = anArgument; + } + + return self; +} + +- (BOOL)isEqualToPerform:(CPDelayedPerform)anOther +{ + if (!anOther || !anOther.isa) + return NO; + + if (_object !== anOther._object) + return NO; + + if (!_selector || !anOther._selector) + return YES; + + if (_selector !== anOther._selector) + return NO; + + if (_argument !== anOther._argument) + return NO; + + return YES; +} + +- (void)perform +{ + try + { + [_object performSelector:_selector withObject:_argument]; + } + catch(ex) + { + CPLog(@"exception %@ raised during delayed perform", ex); + } +} + +@end + +@implementation CPRunLoop(CPDelayedPerform) + +- (void)invalidateTimerWithDelayedPerform:(CPDelayedPerform)aDelayedPerform +{ + for (var aKey in _timersForModes) + { + if (!_timersForModes.hasOwnProperty(aKey)) + continue; + + var timersForMode = _timersForModes[aKey]; + for (var i = 0, count = [timersForMode count]; i < count; i++) + { + var aTimer = [timersForMode objectAtIndex:i], + userInfo = [aTimer userInfo]; + + if ([userInfo isKindOfClass:CPDelayedPerform] && [userInfo isEqualToPerform:aDelayedPerform]) + [aTimer invalidate]; + } + } +} + +@end + +@implementation CPObject(CPDelayedPerform) + ++ (void)cancelPreviousPerformRequestsWithTarget:target selector:(SEL)selector object:argument +{ + var aDelayedPerform = [CPDelayedPerform delayedPerformWithObject:target selector:selector argument:argument]; + + [[CPRunLoop currentRunLoop] invalidateTimerWithDelayedPerform:aDelayedPerform]; +} + ++ (void)cancelPreviousPerformRequestsWithTarget:target +{ + var aDelayedPerform = [CPDelayedPerform delayedPerformWithObject:target selector:NULL argument:nil]; + + [[CPRunLoop currentRunLoop] invalidateTimerWithDelayedPerform:aDelayedPerform]; +} + ++ (void)_delayedPerform:(CPTimer)aTimer +{ + var aDelayedPerform = [aTimer userInfo]; + + [aDelayedPerform perform]; +} + ++ (void)object:object performSelector:(SEL)selector withObject:argument afterDelay:(CPTimeInterval)delay inModes:(CPArray)modes +{ + var aDelayedPerform = [CPDelayedPerform delayedPerformWithObject:object selector:selector argument:argument], + aTimer = [CPTimer timerWithTimeInterval:delay target:[CPObject class] selector:@selector(_delayedPerform:) userInfo:aDelayedPerform repeats:NO]; + + for (var i = 0, count = [modes count]; i < count; i++) + [[CPRunLoop currentRunLoop] addTimer:aTimer forMode:[modes objectAtIndex:i]]; +} + +- (void)performSelector:(SEL)selector withObject:object afterDelay:(CPTimeInterval)delay +{ + [[self class] object:self performSelector:selector withObject:object afterDelay:delay inModes:[CPArray arrayWithObject:CPDefaultRunLoopMode]]; +} + +- (void)performSelector:(SEL)selector withObject:object afterDelay:(CPTimeInterval)delay inModes:(CPArray)modes +{ + [[self class] object:self performSelector:selector withObject:object afterDelay:delay inModes:modes]; +} + +@end diff --git a/Foundation/CPRunLoop.j b/Foundation/CPRunLoop.j index 056aa01e9..f12e4642c 100644 --- a/Foundation/CPRunLoop.j +++ b/Foundation/CPRunLoop.j @@ -270,7 +270,6 @@ var CPRunLoopLastNativeRunLoop = 0; aTimer._lastNativeRunLoopsForModes[aMode] = CPRunLoopLastNativeRunLoop; - // FIXME: Hack for not doing this in CommonJS if ([CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound) { @@ -308,6 +307,7 @@ var CPRunLoopLastNativeRunLoop = 0; nextTimerFireDate = _nextTimerFireDatesForModes[aMode]; // Perform Timers if necessary + if (_didAddTimer || nextTimerFireDate && nextTimerFireDate <= now) { _didAddTimer = NO; @@ -326,12 +326,16 @@ var CPRunLoopLastNativeRunLoop = 0; _timersForModes[aMode] = nil; - // Loop through timers looking for ones that had fired + // If we're running in CommonJS (unit tests) we shouldn't wait for at least 1 native run loop + // since those will never happen. + var hasNativeTimers = [CFBundle.environments() indexOfObject:("Browser")] !== CPNotFound; + + // Loop through timers looking for ones that had fired while (index--) { var timer = timers[index]; - if (timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop && timer._isValid && timer._fireDate <= now) + if ((!hasNativeTimers || timer._lastNativeRunLoopsForModes[aMode] < CPRunLoopLastNativeRunLoop) && timer._isValid && timer._fireDate <= now) [timer fire]; // Timer may or may not still be valid diff --git a/Foundation/Foundation.j b/Foundation/Foundation.j index ad1e4637a..4ce7d451c 100755 --- a/Foundation/Foundation.j +++ b/Foundation/Foundation.j @@ -33,14 +33,15 @@ @import "CPDateFormatter.j" @import "CPDecimal.j" @import "CPDecimalNumber.j" +@import "CPDelayedPerform.j" @import "CPDictionary.j" @import "CPEnumerator.j" @import "CPError.j" @import "CPException.j" @import "CPExpression.j" @import "CPFormatter.j" -@import "CPIndexSet.j" @import "CPIndexPath.j" +@import "CPIndexSet.j" @import "CPInvocation.j" @import "CPJSONPConnection.j" @import "CPKeyedArchiver.j" diff --git a/Tests/Foundation/CPRunLoopTest.j b/Tests/Foundation/CPRunLoopTest.j new file mode 100644 index 000000000..321795e52 --- /dev/null +++ b/Tests/Foundation/CPRunLoopTest.j @@ -0,0 +1,52 @@ +@import +@import +@import + +@implementation CPRunLoopTest : OJTestCase +{ +} + +- (void)testDelayedPerform +{ + var aPerformer = [Performer new]; + + [aPerformer performSelector:@selector(performWithObject:) withObject:5 afterDelay:0]; + [aPerformer performSelector:@selector(performWithObject:) withObject:1 afterDelay:0 inModes:[CPDefaultRunLoopMode]]; + [aPerformer performSelector:@selector(performWithObject:) withObject:10 afterDelay:9999 inModes:[CPDefaultRunLoopMode]]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + + [self assertTrue:[aPerformer.callArgs containsObject:5]]; + [self assertTrue:[aPerformer.callArgs containsObject:1]]; + [CPObject cancelPreviousPerformRequestsWithTarget:aPerformer]; + + var performer1 = [Performer new], + performer2 = [Performer new]; + + [performer1 performSelector:@selector(performWithObject:) withObject:5 afterDelay:0 inModes:[CPDefaultRunLoopMode]]; + [performer2 performSelector:@selector(performWithObject:) withObject:10 afterDelay:0 inModes:[CPDefaultRunLoopMode]]; + [performer1 performSelector:@selector(performWithObject:) withObject:1 afterDelay:0 inModes:[CPDefaultRunLoopMode]]; + [CPObject cancelPreviousPerformRequestsWithTarget:performer1]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + + [self assert:nil equals:performer1.callArgs]; + [self assert:[10] equals:performer2.callArgs]; +} + +@end + +@implementation Performer : CPObject +{ + CPArray callArgs; +} + +- (void)performWithObject:(id)anObject +{ + if (!callArgs) + callArgs = []; + + [callArgs addObject:anObject]; +} + +@end