Here's a code snippet I'm trying to get to work, but its loop won't stop the way that I want it to:
- (IBAction)methodName:(UIButton*)sender
{
[self loopMethod];
}
-(void) loopMethod
{
for( int index = 0; index < loopLimit; index++ )
{
//code I want to execute
[self performSelector:#selector(loopMethod)
withObject:nil
afterDelay:2.0];
}
}
The code just keeps looping even though I've made the for loop finite. What I want is for the code to execute, pause for two seconds, and then run the loop while the int value is less than the loopLimit I've set.
It's been hinted that this performSelector:withObject:afterDelay: method may not be the right thing to use here but I'm not sure why or what is better to use here.
Any illuminating suggestions?
What's happening here is that the loop is running as quickly as possible, and the performSelector:... calls are happening at that speed. Then, at 2.0001, 2.0004, 2.0010, ... seconds later, method gets called.
The other thing (now that you've edited to make clear that the performSelector:... is ending up calling the same method that it's in, is that the value of your loop's index variable isn't saved between calls. Every time loopMethod is run, the code in the loop starts from the beginning: index is set to zero and counts up. That means that every time the method is run, you end up with loopLimit new calls pending, 2 seconds from then. Each one of those calls in turn spawns a new set, and so on, ad infinitum.
Every run of the loop is in fact finite, but the loop keeps getting run. You need some way to signal that the loop needs to stop, and you can't do that entirely within the loop method. You could put the counter (your index variable) into an ivar; that would make its value persistent across calls to loopMethod, but I think you want to look into using an NSTimer that repeats:
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(method:) userInfo:nil repeats:YES];
If you stick this into an ivar, you can keep track of how many times it fires and stop it later. There's a number of posts already on SO about updating text fields in a loop, using a timer like this: https://stackoverflow.com/search?q=%5Bobjc%5D+update+text+field+timer
The reason it doesn't run every 2 seconds is because you are running through the loop, and shooting off that selector after a 2 second delay. In other words, there is no delay in between the loop. If i had to guess, it probably waits 2 seconds, then fires loopLimit times, correct?
For your function to work the way you want it to, it would need to be recursive.
-(void) methodName
{
if(index < loopLimit) {
//code you want to execute
[self performSelector:#selector(**methodName**) withObject:nil afterDelay:2.0];
}
index++;
}
This is a pretty awkward way to do this. An NSTimer is typically what you would use here instead, then you can stop the timer when you are done.
In a function you start the timer like this:
[self setIndex:0];
[self setTimer:[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(method:) userInfo:nil repeats:YES]];
then this method gets called every time:
-(void)method:(NSTimer *)timer {
if(index >= loopLimit) {
[[self timer] invalidate];
}
//Code you want to execute
}
Related
I want that my ball gets faster in my game. in pos you can choose the speed of the ball. But I want the ball getting faster every 5th second. pos = CGPointMake(5.0,4.0); after 5 seconds 5.0 should turn into 6.0 and 4.0 into 5.0.
I have a Timer which is named MainInt. MainInt is a counter and it counts the time how long you're playing without loosing.
There's also a label which shows the timer.
(IBOutlet UILabel *seconds; .h)
.m
-(void)viewDidLoad {
[lastTime setHidden:YES];
[super viewDidLoad];
// X Speed Y Speed
pos = CGPointMake(5.0,4.0); // <- these numbers (add 1 each every 5 sec.)
Speedy = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(doThis) userInfo:nil repeats:YES];
}
/*
-(void)doThis {
if(MainInt % 5 == MainInt) //True every 5th second
{
pos = CGPointMake();
}
}
*/
Well, for one thing, MainInt % 5 == MainInt is false after 5 seconds for the rest of eternity. 1-4 are the only values of MainInt that would ever make that true. Consider reviewing how the modulo operator works. Even if you change this to MainInt % 5 == 0, which is correct, you still have to ask yourself why you're checking for anything there regarding an external asynchronous time value. All this will do is, if the timer is not in synch, force it to not do anything every single time it is called because you already set it's delay between calls to 5 seconds. If you want everything to synch up nicely, you should probably have one main NSTimer or CADisplayLink in charge of a game loop where you can call methods that need to update your game's state every frame.
If you really want to do it this way with separate timers for everything and try to keep them in synch with your main time value, then here you go.
//replace timer line with this one
Speedy = [NSTimer scheduledTimerWithTimeInterval: 0.1 target:self selector:#selector(doThis) userInfo:nil repeats:YES];
//replace body of "doThis" with the following
if(MainInt % 5 == 0) //True every 5th second
{
pos = CGPointMake(pos.x + 1, pos.y + 1);
}
Also, you should follow some sort of naming convention and try to name things more clearly. If pos is the speed of the ball, shouldn't it be named velocity instead of position? What is Speedy, an instance variable? If so, why is it uppercase? Plus, Speedy is one of the most unclear name's I've ever heard; it's not a pet, it's a variable. Same thing for MainInt. In general, reserve uppercase names for class names and use camel case for instance variables, methods and functions. It'll make your code clearer. :)
Is there any way to add a delay of a fraction of a second to a loop (e.g. A for loop). i.e. I would like a short delay after each iteration.
I know that cocos2d allows you to schedule selecters with a delay. But I am not sure how this could be used in this case.
I also know that sleep is costly and not advisable.
Any suggestions?
You should not use NSTimers in cocos2d. It will cause troubles if you want to have possibility to pause your game.
If you want to loop some action with fixed delay between iterations, you can freely use scedule:interval: method with needed delay.
[self schedule:#selector(methodToScedule) interval:yourDelay]
Or if you have to do random delay, you can use sequenses of cocos2d actions. For example
- (void) sceduleMethod
{
// do anything you want
ccTime randomDuration = // make your random duration
id delayAction = [CCDelayTime actionWithDuration: randomDuration];
id callbackAction = [CCCallFunc actionWithTarget:self selector:#selector(scheduleMethod)];
id sequence = [CCSequenece actionOne: delayAction actionTwo: callbackAction];
[self runAction: sequence];
}
in this case you must call your method only once. Then it will call itself with given delay.
You could use C's sleep function:
sleep(seconds);
But you could also look at UITimer, or possibly a block-based performSelector:withObject:afterDelay: method.
See this for more NSObject-based methods: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/performSelector:withObject:afterDelay
I want my NSTimer to speed up each time it's run:
-(void)runtimer {
int delay = delay - 1;
[NSTimer scheduledTimerWithTimeInterval:(delay)
target:self
selector:#selector(timerTriggered:)
userInfo:nil
repeats:YES];
}
But this doesn't work. How can I make the delay keep getting smaller and smaller?
I needed this myself and wrote a component CPAccelerationTimer (Github):
[[CPAccelerationTimer accelerationTimerWithTicks:20
totalDuration:10.0
controlPoint1:CGPointMake(0.5, 0.0) // ease in
controlPoint2:CGPointMake(1.0, 1.0)
atEachTickDo:^(NSUInteger tickIndex) {
[self timerTriggered:nil];
} completion:^{
[self timerTriggered:nil];
}]
run];
This calls -timerTriggered: 20 times, spread out over 10 seconds, with ever-decreasing delays (as specified by the given Bézier curve).
Every time this method is run, you make a new variable called delay, then try to set it to itself minus 1. This is Undefined Behavior (the variable was not initialized to anything), and is likely to result in a garbage value for delay.*
You need to store the delay in an instance variable.
- (void) runTimer {
// You are declaring a new int called |delay| here.
int delay = delay - 1;
// This is not the same |delay| that you have declared in your header.
// To access that variable, use:
delay = delay - 1;
*A sinus infestation by evil-aligned supernatural beings is also a possibility.
You have to declare the delay somewhere, like in the class interface or as a static variable.
Also, create a new timer every time, instead of having it repeat.
int delay = INITIAL_DELAY;
-(void)runtimer{
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)(delay--) target:self selector:#selector(runTimer:) userInfo:nil repeats:NO];
}
You cannot change the fire interval of the timer once you have created it. If you want a different interval you must invalidate the previous timer (hence you should keep a reference to it), and create a new timer with a different interval.
I am looking for an Objective-C function to call a function to update the UI at a specified interval (between 2 and 5 seconds). Something that can be used (roughly) like this pseudocode:
array[String]; // already populated
function1(){
if (array.hasMoreElements()){
call function2() in x seconds;
}
}
void function2(){
update gui with next string in the array;
function1();
}
I simply can't use sleep() for x seconds, because the GUI will become unresponsive; and I can't create a new thread to update the GUI because the iOS UI elements are not thread safe.
I have researched ualarm(), but it is old and very crude, and someone told me that there is a similar utility in the iOS library; however I have not been able to find it.
You're talking about Obj-C but writing something like C pseudocode. If you're really writing normal Obj-C, the -performSelector:withObject:afterDelay is the general family you want that drops in here. (You can use "blocks" on iOS 4.x and above). For example in your use case:
- (void)displayNextString
{
if ([strings count] > 0) {
[self updateUIwithString:[strings objectAtIndex:0]];
[strings removeObjectAtIndex:0];
[self performSelector:#selector(displayNextString) withObject:nil afterDelay:2.0];
}
}
You may use NSTimer. This API fits perfectly what you want to do.
You can simply define a method like
-(void)updateUI {
//update interface
}
then do something like
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:#selector(updateUI) userInfo:nil repeats:YES];
This way the timer will continue call the method you set. Check the reference to see how to stop or invalidate the timer.
I have a program that download tons of files from the net
I have several threads downloading data.
After each data is called, I need to update the managed object contexts
If 10 threads finish loading at about the same time, then the managed object context will get run 10 times.
The truth is I only need to run it once.
What I want to do is to create a method that accept a block.
What should I do to make a function that receive a block, but if that block has been run less than 1 second ago, it won't run the block but instead would postpone the second running till 1 seconds no matter how often the function is called.
Mike Ash has already implemented a timer class for this.
You need to init it with a behaviour type, depending on the exact behaviour you want:
MABGTimerDelay means every time you call afterDelay:1.0 do:^{ /*code*/ } it will set the fire date back so that it only gets run a full second after the last call.
MABGTimerCoalesce means every time you call afterDelay:1.0 do:^{ /*code*/ } it will set the fire date back so that it only gets run a full second after the first call.
If it's already been run, both behaviours will allow you to run it again, but only after the delay has passed again.
If the block that gets run is always the same, you could have a loop running on a one second interval that checks a boolean value and only executes the block if the boolean value is YES. Something like this:
BOOL needsUpdate;
-(void) loop {
if (needsUpdate) {
//Run Block
needsUpdate = NO;
}
[self performSelector:#selector(loop) withObject:nil afterDelay:1.0];
}
When the threads finish loading, you just set needsUpdate = YES and the loop takes care of the rest.
When a thread finished call the method in the main thread. In that method make a timer with 1 second delay.
- (void)threadDidFinish
{
if (_saveTimer != nil)
{
_saveTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(saveTimerDidFire) userInfo:nil repeats:NO];
}
}
- (void)saveTimerDidFire
{
[_saveTimer invalidate];
_saveTimer = nil;
// save the changes
}
This code will ensure you will save every second regardless of the number of times threadDidFinish was called. NSTimer* _saveTimer is an instance variable.