New: - CPObject performSelector:withObject:afterDelay:.

Also - CPObject cancelPreviousPerformWithTarget: and other related methods.
This commit is contained in:
Alexander Ljungberg
2013-03-05 18:52:06 +00:00
parent 21595cdebc
commit f4b08a9ce2
4 changed files with 214 additions and 4 deletions
+153
View File
@@ -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
+7 -3
View File
@@ -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
+2 -1
View File
@@ -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"
+52
View File
@@ -0,0 +1,52 @@
@import <Foundation/CPObject.j>
@import <Foundation/CPRunLoop.j>
@import <OJUnit/OJTestCase.j>
@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