Everytime I am finished with my NSTimer, I want to invalidate it and create a new interval but it keeps the old interval as well as new interval. I want to invalidate NSTimes once I click the offButton. The timer stops printing "Working" but when I call my method with a different interval, it prints "Working" for both intervals.
My code is something like this:
-(void) fireTimer{
NSString *textValue = [sliderLabel text];
float value = [textValue floatValue];
[NSTimer scheduledTimerWithTimeInterval:value target:self selector:#selector(vibrate:) userInfo:nil repeats:YES];
}
- (void) vibrate:(NSTimer*)timer {
if(_offButton.selected){
[timer invalidate];
timer=nil;
}
NSLog(#"Working");
}
You aren't following the MVC design pattern by getting your values directly from the UITextField. Those values should be stored in a model object, with the MVC pattern being used to get any new values from the text field into the model. Your current implementation is very delicate and will break in the slightest breeze. It also requires this code to have access to the UI elements, which is very inflexible; it will be better to give it access to just the model object.
Store the NSTimer * as an instance variable, and note that if you are using ARC then the NSTimer retains the target (!!) so make this instance variable __weak to break the retain-cycle.
If you want your timer to repeat then there is no need to reset it at all; this only needs to be done if the user changes the time (see point 1!).
Move the if (button_selected) do_vibrate; code into the timer fired method.
The invalidation code that you use itself is correct. But it would be easier for you to keep a reference to your timer as an ivar or property.
In that case you would definetly avoid making multiple instances of a timer.
Related
So to initiate an NSTimer we do this:
timer = [NSTimer scheduledTimerWithTimeInterval:0.35 target:self selector:#selector(timerMethod) userInfo:nil repeats:YES];
But what if I want this to run every 0.3 - 0.7 seconds (randomly). I can't do arc4random because then it would choose a number and stick to it.
The only way I've thought of is invalidating it every time it run the 'timerMethod' and then setting a new random time for it but I'm concerned that that will have an effect on the performance.
Is there another way, better to do this?
Instead of using a timer, use a series of [self performSelector:#selector(timerMethod) withObject:nil afterDelay:<random value>] calls. It'll look roughly like tail recursion — timerMethod will routinely schedule a future call to itself somewhere within it.
Also be mindful of retain cycles. Both NSTimer and performSelector:... retain their targets. You could either decline to use either and instead use dispatch_after having captured only a weak reference, or use an approximate two-stage deallocation where a non-dealloc call explicitly invalidates the timer or sets a flag to tell you not to schedule another call to timerMethod.
The GCD solution would look like:
- (void)timerMethod
{
// schedule the next call to timerMethod, keeping only a weak
// reference to self so as not to extend the lifecycle
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(<random value> * NSEC_PER_SEC));
__weak typeof(self) weakSelf = self;
dispatch_after(popTime, dispatch_get_main_queue(),
^{
[weakSelf timerMethod];
});
// ... and do whatever else here ...
}
If you avoid the convenience method and use the NSTimer init method, you get a chance to setTolerance
Another alternative is to create a makeTimer method. In it invalidate the timer if it is not nil. Create a new timer using the init method and set a random interval.
Use the makeTimer method to make your timer.
I cannot seem to work this one out. Here is my set up:
I have a function called requestDataWithCompletion:(someBlock)block. I call it when the class is initialised. The function requests certain motion data. I want to do this periodically, therefore, the first time I call this function, I specify some completion code which sets up a timer that re-calls this function periodically. The timer calls it via another function requestDataWithoutCompletion which simply calls the requestDataWithCompletion but with an empty block (so I don't keep creating timers);
- (void) requestDataWithCompletion:(someBlock)block {
// BREAK POINT 1
[self.motionManager queryActivityStartingFromDate:start toDate:[NSDate date] toQueue:self.queue withHandler:^(NSArray *activities, NSError *error) {
// BREAK POINT 2
// do some processing;
block();
}];
}
The block simply creates a timer on the main queue, which periodically recalls this function, but with no completion (since I don't want to keep creating more timers).
block = ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval
target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
});
}
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion^{;}];
}
The amazing thing is that despite this set up, my app is creating timer after timer! I can't understand why.
I have placed break points in requestDataWithCompletion method. One is outside the block submitted to NSOperationQueue to get activity data (BREAKPOINT 1) and one is inside the block submitted to NSOperationQueue. (BREAKPOINT 2). Basically it shows that each time the method is called by the timer, BREAKPOINT 1 has an empty completion block (as it should be) but then strangely BREAKPOINT 2 has the completion block I submitted when I first called the function when initialising the class. Therefore, it continues to create a new timer each time the function is called by the timer. Over time, this means a massive number of timers and then the app crashes!
I have a feeling this is something to do with NSOperationQueue, but I really don't know what it could be.
In your initialisation (or when you first want to get the data and then continue getting it):
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
[self.timer fire]; //get the activity data immediately.
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion:^{}];
}
With your original requestDataWithCompletion: method. (though you could get rid of requestDataWithCompletion: and put it's code directly in requestDataWithoutCompletion if you're not using it elsewhere)
In the sample tutorial I found, I noticed a timer made using NStimer class: it was implemented directly into viewController.m
I tried to make it as "separate" obj class in it's own timer.m and relative header.
This is what i got
#import "Timer.h"
#implementation Timer
-(void) startTimer{
seconds = 31;
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(subtractTime:) userInfo:nil repeats:YES];
NSLog(#"%i",seconds);
}
-(void)subtractTime: (Timer*)myTimer{
seconds--;
NSLog(#"seconds %i",seconds);
if (seconds == 0) {
[myTimer invalidate];
}
}
#end
According to the output in NSLog, the countdown itself works perfectly; my issues start when i try to display it into a label using the form label.text = into the viewController.m
(here's just the method implementation part)
(Xcode gives me no error bout #implementation part both in Timer.h and viewController.h, also project build runs ok but the countdown into the label is locked to 0)
-(void)setupGame{
count = 0;
scoreLabel.text = [NSString stringWithFormat:#"Score:\n%i",count];//these strings are to make a score label increasing of 1 every time a button's pressed
Timer *newTimer = [[Timer alloc]init]; //i create a new obj of my Timer class
[newTimer startTimer];
[newTimer subtractTime:(Timer*) myTimer]; //i set the methods i created
timerLabel.text = [NSString stringWithFormat:#"%i",seconds]; //??? i don't know which var put here, actually, i cannot get here the refreshing seconds to make timerLabel changing
NSLog(#"newTimer %#",myTimer);//just a try to see this output, it's not what i need of course
}
What am i doing wrong?
There are a couple of issues here. Firstly make Timer an instance variable of the view controller as when you do this:
-(void)setupGame{
// blah
Timer *newTimer = [[Timer alloc]init];
// blah
}
The Timer object will be destroyed when this method returns, as the object goes out of scope.
The second problem is that you need to update the UI control when the time changes, so perhaps pass a reference to the UI control (UILabel or whatever it is), and store this as an instance variable of the Timer class, so the timer object can update it itself.
In this particular case I don't see the value of a separate Timer object as it would be easier to implement what you want just within the view controller class.
As Trojanfoe says, you are over-complicating this. Make your view controller create the timer and be done with it.
If you are bound and determined to create a custom class to manage a timer then you have a bunch of work to do.
I would NOT pass the label to the Timer object, as that breaks the encapsulation of the view controller. You should always treat a view controller's views as private to that view controller.
Instead, here's what I would do:
You need to add a Timer instance variable to your view controller, as Trojanfoe says.
You need to define a TimerProtocol. In that protocol, define a "timerFired" method, that would include the remaining time value as a parameter. You probably also want a "timerFinished" method
When you create a Timer object, set yourself up as the delegate of that timer.
Rewrite your Timer object's subtractTime method to send a message to the delegate with the remaining time value. Then in the view controller's timerFired method, update the label.
In your view controller's timerFinished method, nil out the instance var to the Timer object so it will be deallocated, since you then done with it.
Again, this is an over-complicated solution that does not add any value. The only reason to do it this way is as a learning exercise, but it is not a good design decision.
I'm working on a project, where i have to update the text of a UILabel really regularly (0.085f). So, I insert the update of the label in a loop like this :
MetresNumber = MetresNumber + 0.25;
DisplayMetres.text = [NSString stringWithFormat:#"%07.0f", MetresNumber];
I precise that "MetresNumber" is a float, and "DisplayMetres" the UILabel.
And this sort of code really really makes bad performances ! It's incredible how slower it goes since i've added those lines.
I made some searches, and found elements like :
[DisplayMetres setNeedsDisplay];
But it didn't change and update the text on the label.
It's in a loop called with :
timer = [NSTimer scheduledTimerWithTimeInterval:0.085 target:self selector:#selector(myLoop) userInfo:nil repeats:YES];
So my question is, could my code be improve, to get better performances, or should i forget my UILabel because it's too slow with ?
Thanks !
(void)setNeedsLayout
Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
Another problem is that a scheduledTimer will not get called while the main thread is tracking touches. You need to schedule the timer in the main run loop.
So instead of doing
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
use
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Also Use Timer selector method like below:
- (void) updateLabel:(id) sender {
MetresNumber = MetresNumber + 0.25;
NSString *text = [NSString stringWithFormat:#"%07.0f", MetresNumber];
dispatch_sync(dispatch_get_main_queue(), ^{
DisplayMetres.text = text;
});
}
There shouldn't be any performance issues when you are just updating a single label every 85ms. First find out what actually causes the lags. Use the Time Profiler instrument.
I think most time will be spent on the string drawing.
Here are some tips how you can optimize your code:
You don't need to call setNeedsLayout or setNeedsDisplay explicitely on the label. Just set the text property (on the main thread of course).
The resulting string in your code will always be a 7-digit integer. Consider using an integer instead of a float. Formatting an integer will be faster.
stringWithFormat can be slow sometimes. You could try using a NSNumberFormatter or simply generate the string with: [#(MetresNumber) stringValue]
In your code the string actually doesn't change everytime the timer fires. Only every 4th time. You can set the time interval to 4*0.085 and replace MetresNumber = MetresNumber + 0.25 with MetresNumber = MetresNumber + 1.
Try using this custom UILabel class
Don't use UILabel at all. Use pre-drawn images for each digit.
Schedule the timer with NSRunLoopCommonModes (see answer from Lightygalaxy)
In a certain view I wish to have a certain method fired when the firing timing is based on an array of NSTimeIntervals (which I keep in as NSArray holding double values).
So I have a method that does the relevant action according to the relevant timing, and within this method I call the same method (recursively) to be fired again in the next scheduled time:
-(void) actionMethod:(int)someDataINeed {
//do something and then...
if (someDataINeed == someDefinitionIHaveToBreakTheLoop) {
return;
}
double nextFire = [self aMethodThatCalculatesTheNextFiringTime];
NSNumber * nextDataINeed = [NSNumber numberWithInt:someDataINeed+1];
NSTimer * aTimer = [NSTimer scheduledTimerWithTimeInterval:nextFire
target:self
selector:#selector(actionMethod:)
userInfo:nextDataINeed
repeats:NO];
}
Well... it doesn't work (if it did, I guess I wouldn't ask about it...).
When I NSLog it, it seems like the time is not running at all and that the timer is called in some sort of loop and not "being fired" according to the timing I had defined. The nextFire double data is correct (according to my definitions).
If I have it wrong, I would appreciate if someone can direct me to how this type of action should be performed.
If I got it right, but simply write it wrong, an eye that catches my "bug" would be appreciated as well...
It's not a memory management problem
When you schedule an NSTimer instance, the run loop retains the timer, so there's no need to worry about retaining it yourself (ARC or not).
The timer also retains its target, so you don't have to worry about a situation where the target is deallocated first. However, if the target is a view, it might receive a timer message after it has been removed from the view hierarchy. In that case you will want to ignore the message.
You can handle this two ways:
In your actionMethod, be sure to check that you still want to perform the action. If not, ignore the message and let everything be cleaned up.
Retain a reference to the timer instance, and call [timer invalidate] when you no longer want to receive timer messages.
If you can be sure that the target will always be around and it will always be OK to receive a message, you don't need to do anything. For example, your app delegate object will be around for the lifetime of your application.
NSTimer's target action
You cannot set a timer to call just any method on any object. The timer method MUST take only one parameter, which is the NSTimer instance that sent the message. You have defined actionMethod: to take an int, which will actually compile and execute, but when the timer fires and the method is called, the value of someDataINeed will be the memory address of the NSTimer object. This isn't very useful.
You need to define your method this way:
- (void)actionMethod:(NSTimer *)timer
To get the someDataINeed value, you can save it as an NSNumber in the timer's userInfo. It is there just so you can add some data to the timer.
NSNumber *number = [timer userInfo];
int someDataINeed = [number intValue];
When you create the timer, you can wrap the nextDataINeed into an NSNumber and set the userInfo this way:
currentTimer = [NSTimer scheduledTimerWithTimeInterval:nextFire
target:self
selector:#selector(actionMethod:)
userInfo:[NSNumber numberWithInt:nextDataINeed]
repeats:NO];
You need to use NSNumber because the userInfo must be an object.
Possible mistake
If applying the above doesn't fix the problem, one thing to check is that aMethodThatCalculatesTheNextFiringTime is returning the time interval since now, and not since a reference date. If you want a timer to fire in three seconds, the value of nextFire must be 3.0. If you accidentally call timeIntervalSinceReferenceDate you'll get a huge number, and the timer will be scheduled to fire several years into the future.
I'm guessing you are using ARC. You don't store the timer in a strong variable, so it gets released before it can fire. Create an ivar "NSTimer * aTimer", then use that instead of a local variable when you instantiate the timer (I'm waffling here since I cannot remember if the runLoop retains it when its scheduled - probably but leaving this here for completeness, as you should call invalidate on the timer if for any reason you get "popped" etc).
Also your timer sends the message: actionMethod:(NSSTimer *)itself, but your action method expects an int. You need to reconcile that.
EDIT:
Change your actionMethod to this format:
-(void)actionMethod:(id)something
{
int foo = 0;
if([something isKindOfClass:[NSNumber class]]) {
foo = [foo intValue];
} else
if([something isKindOfClass:[NSTimer class]]) {
foo = ... // however you get your value
} else
assert(!"Yikes! Wrong class");
}
...
Then you can send it a NSNumber or have the timer call it with the timer. You can pull out the int from the NSNumber, or get a value from the timer: