I have an NSArray that I would like to display each object at a specific time interval, for example:
NSArray *array = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
and output it:
for (object in array) {
NSLog(#"%#", object);
// wait 3 seconds then show next object
}
so it should do this:
foo
... wait three seconds
bar
... wait three seconds
baz
... wait three seconds
repeat
how can i do this?
The simplest approach would be to use Blocks...
You can explore how to do this with a Blocks using third party categories such as :
https://github.com/zwaldowski/BlocksKit
As for a solution without blocks:
#property (nonatomic, strong) NSArray *myArray;
#property (nonatomic) NSInteger iteration;
- (void)awakeFromNib //Or equivalent method called when you want to start scheduling..
{
self.myArray = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
self.iteration = 0;
[NSTimer scheduledTimerWithTimeInterval:3
target:self
selector:#selector(fireEvent)
userInfo:nil
repeats:YES]; //Will Fire every 3 seconds...
}
-(void)fireEvent
{
NSLog(#"%#", [self.myArray objectAtIndex:self.iteration]);
self.iteration++;
if (self.iteration>=self.myArray.count) self.iteration=0;
}
If you with to invalidate the timer after all elements from the array are shown. Do the following :
-(void)awakeFromNib //Or equivalent method called when you want to start scheduling..
{
self.myArray = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
self.iteration = 0;
[NSTimer scheduledTimerWithTimeInterval:3
target:self
selector:#selector(fireEvent:)
userInfo:nil
repeats:YES]; //Will Fire every 3 seconds...
}
-(void)fireEvent:(NSTimer*)timer
{
NSLog(#">>>> %#", [self.myArray objectAtIndex:self.iteration]);
self.iteration++;
if (self.iteration>=self.myArray.count) {
self.iteration=0;
[timer invalidate];
}
}
NOTE: The latter code only adds a ':' to the #selector(fireEvent:). And reads the NSTimer reference in the fireEvent as a parameter.
Depending on the surrounding context, you might be able to do what you want by letting an NSRunLoop run for 3 seconds. Alternatively, and perhaps a bit safer and saner, you could use an NSTimer to run an action every 3 seconds.
I'd originally suggested breaking up the loop, logging the first element, then perform the same action on the rest of the array after a 3-second delay, using either something like performSelector:withObject:afterDelay: or dispatch_after, but as danielbeard pointed out, NSTimer provides a much more straight-forward way to cancel the action (cancellation is possible with the other methods, but requires planning and manual work).
Edit: If I were going to do this with an NSTimer, I'd probably use this NSTimer+Blocks category and do something like (untested code):
__block NSUInteger index = 0;
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:3.0 block:^{
NSLog(#"%#", array[index]);
index++;
if (index >= [array count]) {
[timer invalidate];
}
} repeats:YES];
Related
I've tried the following:
- (void)setupTimer {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(doSomething) userInfo:nil repeats:YES];
objc_setAssociatedObject(self, &key, timer, OBJC_ASSOCIATION_RETAIN);
[timer invalidate];
}
- (void)doSomething { /* ... */ }
- (void)afterDoingSomething { [objc_getAssociatedObject(self, &key) invalidate]; }
Yet, the timer doesn't tick (I didn't expected it to do so; this code just didn't look much right to me) for some reason. Is there a way to make it... work?
You're invalidating the timer immediately after creating and scheduling it. Remove the [timer invalidate]; line in -setupTimer:
- (void)setupTimer {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(doSomething) userInfo:nil repeats:YES];
objc_setAssociatedObject(self, &key, timer, OBJC_ASSOCIATION_RETAIN);
}
From the documentation:
[invalidate] stops the receiver from ever firing again and requests
its removal from its run loop.
Am I correct in assuming that this code is in a category? Otherwise, I can't think of a good reason to make the timer an associated object instead of just using a regular instance variable for it.
You've must have mistaken invalidate and release.
objc_setAssociatedObject does release for you when you set next item.
I don't think it is a problem with setting a timer as an associative reference.
You are killing your timer before it actually start (invalidate does so).
Your - (void)doSomething method should have different signature: - (void)doSomething: (NSTimer*)theTimer.
You may be leaking memory, you should do objc_setAssociatedObject(self, &key, nil, OBJC_ASSOCIATION_RETAIN); in your afterDoingSomething.
HI, I have a static NSMutableArray* staticArray in an NSOperation subclass (in myOperation.m) and a method:
static NSMutableArray *staticArray =
nil;
+(void) initialize {
staticArray = [[NSMutableArray alloc] init];
}
-(void) addStrToStaticArray:(NSString*)aStr {
if([staticArray indexOfObject:aStr] == NSNotFound) {
[staticArray addObject:aStr];
[staticArray performSelector:#selector(removeObject:)
withObject:aStr
afterDelay:30.];
}
}
I call the above method and after that the operation finishes execution. The problem is that aStr is never removed from the array. What am I missing ? Thanks...
Based to Justin suggestions, I can now delayed remove an object from an array invoking the method from inside a NSThread, NSOperation despite their existence at the time of the removal:
NSMethodSignature * mySignature = [NSMutableArray instanceMethodSignatureForSelector:#selector(removeObject:)];
NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature:mySignature];
[myInvocation setTarget:staticArray];
[myInvocation setSelector:#selector(removeObject:)];
[myInvocation setArgument:&aStr atIndex:2];
//At this point, myInvocation is a complete object, describing a message that can be sent.
NSTimer *timer = [NSTimer timerWithTimeInterval:90.
invocation:myInvocation
repeats:NO];
if(timer) {
NSRunLoop *mainRL = [NSRunLoop mainRunLoop];
[mainRL addTimer:timer forMode:NSDefaultRunLoopMode];
}
The aStr will be removed from staticArray after 90 seconds. For details...
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DistrObjects/Tasks/invocations.html
use timer (CFRunLoopTimer/NSTimer) and run loop (CFRunLoop/NSRunLoop) apis to accomplish this.
in that case, you create a timer, and add it to the main run loop.
this would also require that you create a function or method for your timer to call. if you choose a method, you could use an NSInvocation instead (if that's what you prefer).
since the data is static, and the operation (presumably) won't exist, you can message via a class method.
Firstly newbie question: What's the difference between a selector and a method?
Secondly newbie question (who would have thought): I need to loop some code based on instance variables and pause between loops until some condition (of course based on instance variables) is met. I've looked at sleep, I've looked at NSThread. In both discussions working through those options many asked why don't I use NSTimer, so here I am.
Ok so it's simple enough to get a method (selector? ) to fire on a schedule. Problem I have is that I don't know how to see instance variables I've set up outside the timer from within the code NSTimer fires. I need to see those variables from the NSTimer selector code as I 1) will be updating their values and 2) will set labels based on those values.
Here's some code that shows the concept… eventually I'd invalidate the timers based on myVariable too, however I've excluded that for code clarity.
MyClass *aMyClassInstance = [MyClass new];
[aMyClassInstance setMyVariable:0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff) userInfo:nil repeats:YES];
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(doSomeOtherStuff) userInfo:nil repeats:YES];
- (void) doStuff {
[aMyClassInstance setMyVariable:11]; // don't actually have access to set aMyClassInstance.myVariable
[self updateSomeUILabel:[NSNumber numberWithInt:aMyClassInstance.myVariable]]; // don't actually have access to aMyClassInstance.myVariable
}
- (void) doSomeOtherStuff {
[aMyClassInstance setMyVariable:22]; // don't actually have access to set aMyClassInstance.myVariable
[self updateSomeUILabel:[NSNumber numberWithInt:aMyClassInstance.myVariable]]; // don't actually have access to aMyClassInstance.myVariable
}
- (void) updateSomeUILabel:(NSNumber *)arg{
int value = [arg intValue];
someUILabel.text = [NSString stringWithFormat:#"myVariable = %d", value]; // Updates the UI with new instance variable values
}
You can use the userInfo parameter to transmit arbitrary object. In this case, you pass aMyClassInstance as userInfo:
MyClass *aMyClassInstance = [MyClass new];
[aMyClassInstance setMyVariable:0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff) userInfo:aMyClassInstance repeats:YES];
In the timer callback (which MUST takes a parameter), you get back the userInfo from the timer and cast it:
- (void) doStuff:(NSTimer *)timer {
MyClass *instance = (MyClass *)[timer userInfo];
[instance setMyVariable:11];
[self updateSomeUILabel:[NSNumber numberWithInt:instance.myVariable]];
}
The neat thing is that the timer retains the userInfo parameter.
One of your questions was asking about the difference between a selector and a method.
A selector “selects” the method to use from an object. Imagine you had some animal classes, say Dog, Cat, and Bird, all subclasses of Animal. They all implement a method called makeSound. Each class will have its own implementation of makeSound, otherwise all of the animals will sound the same. So, all animals have a different method for making a sound, but you get each animal to make its sound using the same selector. You are selecting the makeSound method of an animal, in other words.
You do have access to instance variables if you set the instance as the target of the timer like so:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:aMyClassInstance selector:#selector(doStuff) userInfo:nil repeats:YES];
The instance (which you've referred to as aMyClassInstance) will be self.
Alternatively you can put aMyClassInstance and any other objects in the userInfo dictionary. You would do that like so:
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
aMyClassInstance, #"a",
bMyClassInstance, #"b",
cMyClassInstance, #"c",
nil];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff:) userInfo:userInfo repeats:YES];
Then, in the doStuff: selector, you can get them back out like so:
-(void) doStuff:(NSTimer*)timer;
{
MyClass* aMyClassInstance = [[timer userInfo] objectForKey:#"a"];
MyClass* bMyClassInstance = [[timer userInfo] objectForKey:#"b"];
MyClass* cMyClassInstance = [[timer userInfo] objectForKey:#"c"];
//do whatever you want here
}
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
How do I use an NSTimer? Can anyone give me step by step instructions?
Firstly I'd like to draw your attention to the Cocoa/CF documentation (which is always a great first port of call). The Apple docs have a section at the top of each reference article called "Companion Guides", which lists guides for the topic being documented (if any exist). For example, with NSTimer, the documentation lists two companion guides:
Timer Programming Topics for Cocoa
Threading Programming Guide
For your situation, the Timer Programming Topics article is likely to be the most useful, whilst threading topics are related but not the most directly related to the class being documented. If you take a look at the Timer Programming Topics article, it's divided into two parts:
Timers
Using Timers
For articles that take this format, there is often an overview of the class and what it's used for, and then some sample code on how to use it, in this case in the "Using Timers" section. There are sections on "Creating and Scheduling a Timer", "Stopping a Timer" and "Memory Management". From the article, creating a scheduled, non-repeating timer can be done something like this:
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(targetMethod:)
userInfo:nil
repeats:NO];
This will create a timer that is fired after 2.0 seconds and calls targetMethod: on self with one argument, which is a pointer to the NSTimer instance.
If you then want to look in more detail at the method you can refer back to the docs for more information, but there is explanation around the code too.
If you want to stop a timer that is one which repeats, (or stop a non-repeating timer before it fires) then you need to keep a pointer to the NSTimer instance that was created; often this will need to be an instance variable so that you can refer to it in another method. You can then call invalidate on the NSTimer instance:
[myTimer invalidate];
myTimer = nil;
It's also good practice to nil out the instance variable (for example if your method that invalidates the timer is called more than once and the instance variable hasn't been set to nil and the NSTimer instance has been deallocated, it will throw an exception).
Note also the point on Memory Management at the bottom of the article:
Because the run loop maintains the timer, from the perspective of memory management there's typically no need to keep a reference to a timer after you’ve scheduled it. Since the timer is passed as an argument when you specify its method as a selector, you can invalidate a repeating timer when appropriate within that method. In many situations, however, you also want the option of invalidating the timer—perhaps even before it starts. In this case, you do need to keep a reference to the timer, so that you can send it an invalidate message whenever appropriate. If you create an unscheduled timer (see “Unscheduled Timers”), then you must maintain a strong reference to the timer (in a reference-counted environment, you retain it) so that it is not deallocated before you use it.
there are a couple of ways of using a timer:
1) scheduled timer & using selector
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:#selector(onTick:)
userInfo: nil repeats:NO];
if you set repeats to NO, the timer will wait 2 seconds before running the selector and after that it will stop;
if repeat: YES, the timer will start immediatelly and will repeat calling the selector every 2 seconds;
to stop the timer you call the timer's -invalidate method: [t invalidate];
As a side note, instead of using a timer that doesn't repeat and calls the selector after a specified interval, you could use a simple statement like this:
[self performSelector:#selector(onTick:) withObject:nil afterDelay:2.0];
this will have the same effect as the sample code above; but if you want to call the selector every nth time, you use the timer with repeats:YES;
2) self-scheduled timer
NSDate *d = [NSDate dateWithTimeIntervalSinceNow: 60.0];
NSTimer *t = [[NSTimer alloc] initWithFireDate: d
interval: 1
target: self
selector:#selector(onTick:)
userInfo:nil repeats:YES];
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer:t forMode: NSDefaultRunLoopMode];
[t release];
this will create a timer that will start itself on a custom date specified by you (in this case, after a minute), and repeats itself every one second
3) unscheduled timer & using invocation
NSMethodSignature *sgn = [self methodSignatureForSelector:#selector(onTick:)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature: sgn];
[inv setTarget: self];
[inv setSelector:#selector(onTick:)];
NSTimer *t = [NSTimer timerWithTimeInterval: 1.0
invocation:inv
repeats:YES];
and after that, you start the timer manually whenever you need like this:
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer: t forMode: NSDefaultRunLoopMode];
And as a note, onTick: method looks like this:
-(void)onTick:(NSTimer *)timer {
//do smth
}
Something like this:
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval: 0.5
target: self
selector: #selector(handleTimer:)
userInfo: nil
repeats: YES];
#import "MyViewController.h"
#interface MyViewController ()
#property (strong, nonatomic) NSTimer *timer;
#end
#implementation MyViewController
double timerInterval = 1.0f;
- (NSTimer *) timer {
if (!_timer) {
_timer = [NSTimer timerWithTimeInterval:timerInterval target:self selector:#selector(onTick:) userInfo:nil repeats:YES];
}
return _timer;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
-(void)onTick:(NSTimer*)timer
{
NSLog(#"Tick...");
}
#end
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:#selector(timerCalled) userInfo:nil repeats:NO];
-(void)timerCalled
{
NSLog(#"Timer Called");
// Your Code
}
The answers are missing a specific time of day timer here is on the next hour:
NSCalendarUnit allUnits = NSCalendarUnitYear | NSCalendarUnitMonth |
NSCalendarUnitDay | NSCalendarUnitHour |
NSCalendarUnitMinute | NSCalendarUnitSecond;
NSCalendar *calendar = [[ NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents = [calendar components: allUnits
fromDate: [ NSDate date ] ];
[ weekdayComponents setHour: weekdayComponents.hour + 1 ];
[ weekdayComponents setMinute: 0 ];
[ weekdayComponents setSecond: 0 ];
NSDate *nextTime = [ calendar dateFromComponents: weekdayComponents ];
refreshTimer = [[ NSTimer alloc ] initWithFireDate: nextTime
interval: 0.0
target: self
selector: #selector( doRefresh )
userInfo: nil repeats: NO ];
[[NSRunLoop currentRunLoop] addTimer: refreshTimer forMode: NSDefaultRunLoopMode];
Of course, substitute "doRefresh" with your class's desired method
try to create the calendar object once and make the allUnits a static for efficiency.
adding one to hour component works just fine, no need for a midnight test (link)
I'm having problems starting & stopping NSTimers. The docs say that a timer is stopped by [timer invalidate];
I have a timer object declared as such
.h
NSTimer *incrementTimer;
#property (nonatomic, retain) NSTimer *incrementTimer;
.m
#synthesize incrementTimer;
-(void)dealloc {
[incrementTimer release];
[super dealloc];
}
-The usual.
When it's needed, my method does the following:
-(void)setGenCount {
if(!condition1 && condition2) {
incrementTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:#selector(incrementBatteryVoltage:)
userInfo: nil
repeats: YES];
}
}
Everything above works fine. However, once that timer does it's job, I want it to invalidate itself. I invalidate the timer because there is an equal decrement method that could be called and would fight against the incrementTimer if it was still active. (Previously, I noticed that my two timers, if active, were acting on the same ivar by increasing & decreasing the value (a sort of fight)... without crashing) The selector called works as follows:
-(void)incrementBatteryVoltage:(NSTimer *)timer {
if(battVoltage < 24.0) {
generatorDisplay.battVoltage += 0.1;
}
if(battery1Voltage == 24.0) {
[timer invalidate];
}
}
I have an equal method that Decrements the battery count. (previously mentioned)
Due to my program design: the interface simulates a voltage display. When the "machine" is turned off, I want all the timers invalidated, regardless of what any voltage value is. I'm doing this by checking to see if the timer is valid.
-(void)deEnergizeDisplays {
if([decrementTimer isValid]) {
[decrementTimer invalidate];
decrementTimer = nil;
}
if([incrementTimer isValid]) {
[incrementTimer invalidate];
incrementTimer = nil;
}
I'm getting numerous "BAD_ACCESS" crashes. The erroneous line call is always pointing toward my [timer isValid] call. It seems that if the timer is invalidated... the pointer
doesn't exist either. I know that the [timer invalidate] message disables the timer and then it is removed from the run loop and then it is released. And my understanding is: it is an autoreleased object per it's naming covention.
My thought are: If I'm sending a retain message, shouldn't the reference still exist? I've tried several combinations, taking away:
timer = nil;
or even instead of:
if([timer isValid])
I tried :
if([timer != nil])
and:
if(timer)
I always get the same crash. Thanks for any help on starting & stopping NSTimers.
UPDATE: See Darren's answer. The problem is that you are not using your property accessor when setting the timers. Instead of:
incrementTimer = [NSTimer ...
You should have:
self.incrementTimer = [NSTimer ...
The self.propertyName = ... syntax will call your accessor method, and thereby automatically retain the object that you send to it (since your property is set up as retain). Simply calling propertyName = ... does not use the property accessor. You are simply changing the value of your ivar directly.
UPDATE #2: After an enlightening conversation with Peter Hosey (see comments), I have removed my earlier suggestion to "never retain or release" your timer object. I have also completely re-written my earlier code because I think the following is a better approach:
Controller.h:
NSTimer *voltageTimer;
float targetBatteryVoltage;
...
#property (nonatomic, retain) NSTimer *voltageTimer;
Controller.m:
#implementation Controller
#synthesize voltageTimer;
- (void)stopVoltageTimer {
[voltageTimer invalidate];
self.voltageTimer = nil;
}
- (void)setTargetBatteryVoltage:(float)target {
[voltageTimer invalidate];
targetBatteryVoltage = target;
self.voltageTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector: #selector(updateBatteryVoltage:)
userInfo: nil
repeats: YES];
}
- (void)updateBatteryVoltage:(NSTimer *)timer {
const float increment = 0.1;
if (abs(battVoltage - targetBatteryVoltage) < increment) {
[timer invalidate];
}
else if (battVoltage < targetBatteryVoltage) {
generatorDisplay.battVoltage += increment;
}
else if (battVoltage > targetBatteryVoltage) {
generatorDisplay.battVoltage -= increment;
}
}
Now, you can simply set a target battery voltage, and the timer magic will happen behind the scenes:
[self setTargetBatteryVoltage:24.0];
Your power-off method would look as follows:
- (void)deEnergizeDisplays {
[self stopVoltageTimer];
}
You need to retain the value assigned to incrementTimer in setGenCount. You can do this automatically by using your synthesized property, which is accessed via self.:
self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval: ...