How to make multiple NSTimers for many class instances? - ios

I am making an iOS application that uses wireless communication. One of its features is checking if the external devices that it is connected with are responding. So what I tried to do, is to make a "Device" class for every connected device, and then for each of them create a NSTimer that would handle the timeouts. And I made it like this:
The "Device" class init:
NSTimer* communicationChecker;
- (id)initWithAddress: (NSString*) address;
{
self = [super init];
if (self)
{
_address = address;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateStatus:) name:NOTIFICATION_STATUS object:nil];
communicationChecker = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(iAmDead:) userInfo:nil repeats:YES];
self.readyToRoll = true;
}
return self;
}
The timer selector:
- (IBAction)iAmDead:(NSTimer*)sender
{
self.readyToRoll = false;
NSLog(#"%# is dead :(", self.address);
}
And the notification selector:
-(void)updateStatus:(NSNotification *) notification
{
NSDictionary* userInfo = notification.userInfo;
NSString* deviceAddress = (NSString*)userInfo[PARAM_DEVICE_ADDRESS];
if ([_address isEqualToString:deviceAddress]) {
self.readyToRoll = true;
[communicationChecker invalidate];
communicationChecker = nil;
communicationChecker = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(iAmDead:) userInfo:nil repeats:YES];
}
}
So how I thought this would work, is that every time that a notification comes for that given device, it would change its "readyToRoll" variable and reset the timer. The problem is that only one device declares that it's dead (when none of them report status), and it's the one that sent the last status report message. I really have no clue how to go about this. What causes this behaviour?

I have solved the problem by moving the NSTimer declaration from the .m file, to .h. By adding the NSTimer as a property (#property NSTimer* communicationChecker;) it is initiated for each device. Everything works as expected now.
I think that the NSTimer was initiating only once earlier, and was only restarted with different parameters. Now each device has its own timer.

Now this might be total wrong, but did you try to initialize your timers like this :
NSTimer *timer = [NSTimer timerWithTimeInterval:kUpdateTimeInterval
target:self
selector:#selector(doStuff)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
That way you ensure that they are added to the mainRunLoop, and not the "currentLoop" as done in the scheduledTimerWithTimeInterval function.
Try and let me know.

Related

How to stop an NSTimer?

I have an NSTimer that I want to be stopped when I leave my vViewVontroller:
The timer is in a method that I call from viewWillAppear :
- (void) myMehtod
{
//timer = [[NSTimer alloc] init];
// appel de la methode chaque 10 secondes.
timer = [NSTimer scheduledTimerWithTimeInterval:10.0f
target:self selector:#selector(AnotherMethod) userInfo:nil repeats:YES];
//self.timerUsed = timer;
}
I call the method stopTimer in viewWillDisappear
- (void) stopTimer
{
[timer invalidate];
timer = nil;
}
PS: I tried the answer of user1045302 in this question but it didn't work:
How to stop NSTimer
The source of the problem probably is that myMehtod is called twice or more times.
Since the method does not invalidate existing timers before setting up the new one you actually have several timers ticking at the same time.
Fix is easy: invalidate old timers before setting up a new one:
- (void)myMehtod
{
[timer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:10.0f
target:self
selector:#selector(anotherMethod)
userInfo:nil
repeats:YES];
}

Crash when checking if NSTimer isValid?

I am getting this exception in the console:
Error:
2015-06-25 23:12:01.841 Copyfeed for Mac[9512:584232]
-[_NSViewLayoutAux invalidate]: unrecognized selector sent to instance 0x6000001657c0
when checking if my timers are valid/and when invalidating them.
if ([_staticTimer isValid]) {
[_staticTimer invalidate];
_selectionTimer =
[NSTimer scheduledTimerWithTimeInterval:2 target:self
selector:#selector(hideHUD) userInfo:nil repeats:NO];
}
if ([_selectionTimer isValid]) {
[_selectionTimer invalidate];
_selectionTimer =
[NSTimer scheduledTimerWithTimeInterval:2 target:self
selector:#selector(hideHUD) userInfo:nil repeats:NO];
}
This is my new code:
if (_selectionTimer != nil) {
[_selectionTimer invalidate];
_selectionTimer = nil;
_selectionTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(hideHUD) userInfo:nil repeats:NO];
}
if (_staticTimer != nil) {
[_staticTimer invalidate];
_staticTimer = nil;
_selectionTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(hideHUD) userInfo:nil repeats:NO];
}
#property (strong )NSTimer *staticTimer;
#property (strong )NSTimer *selectionTimer;
Now getting this error when I debug with zombie objects on.
2015-06-26 00:39:45.523 Copyfeed for Mac[11191:824502] ***
-[CFRunLoopTimer release]: message sent to deallocated instance 0x608000175e40
There are a couple of potential issues here.
if (_staticTimer != nil) {
[_staticTimer invalidate];
_staticTimer = nil;
_selectionTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(hideHUD) userInfo:nil repeats:NO];
}
What you are doing here is overriding the selectionTimer even though it might still contain a timer that is still scheduled in the run loop. So if you reset the property here, you should also make sure to call [_selectionTimer invalidate] before doing so.
Depending on what you are doing when the timer is firing, this could explain the crash on CFRunLoopTimer.
A general advice that turned out to be very helpful for me when working with NSTimer: I would recommend declaring all the properties that hold a scheduled timer as weak as they are retained by the run loop anyway. This way, you don't need to explicitly set them to nil after invalidating them but instead you can simply call invalidate every time you want to get rid of it and it will also automatically become nil once it has fired by the run loop, releasing all the data it might hold on to.
Note that this would still require you to call invalidate in case you want to cancel a timer or before replacing one, but you no longer need to set it to nil after doing so.
After you invalidate it, you should set an NSTimer object to nil.
invalidate method also does a release.
If you didn't do that, calling a method on it like isValid could cause your crash.
if (_selectionTimer != nil) {
[_selectionTimer invalidate];
_selectionTimer = nil;
// do something
}
Check here
if (_staticTimer != nil) {
[_staticTimer invalidate];
_staticTimer = nil;
//_selectionTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(hideHUD) userInfo:nil repeats:NO];
_staticTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(hideHUD) userInfo:nil repeats:NO];
}
From the documentation:
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.
So you should make the timer weak instead of strong

Handle NSNotification while scrolling a inner UITableView

I need some UIViewControllers that receive a NSNotification from the app delegate. It's like a timer, but every UIViewController handle your way. My problem is: when I interact with user interface, the UIViewController doesn't receive the notification, causing problems.
Here is my code in AppDelegate:
-(void)updateCounter:(NSTimer *)theTimer{
[[NSNotificationCenter defaultCenter] postNotificationName:TimeTickNotification object:nil];
}
//*called by some trigger in the app
-(void) startTimer{
timer = [NSTimer
scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
I am handling the notifications in each UIViewController like this:
-(void) updateGlobalTime:(NSNotification *) notification{
totalTime = [NSNumber numberWithFloat:([ficha.tempoTotal floatValue] + STEP)];
}
-(void) viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateGlobalTime:)
name:TimeTickNotification
object:nil];
}
What should I do to interact with UI and update it at same time? Maybe the NSNotification is not being thrown while user interacts with UI.
You need to make sure you're updating any UI on the main thread. If you want to update the UI to have the new totalTime in a label or something, make sure the setText: function is running on the main thread. You can accomplish that with GCD, like this:
-(void) updateGlobalTime:(NSNotification *) notification{
totalTime = [NSNumber numberWithFloat:([ficha.tempoTotal floatValue] + STEP)];
// Update label on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[label setText:totalTime];
});
}
The solution was to use NSRunLoop, as following:
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
timer = [NSTimer
scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
[runloop addTimer:timer forMode:NSRunLoopCommonModes];
[runloop addTimer:timer forMode:UITrackingRunLoopMode];

How do I see if NSTimers are stacking and how to prevent it?

Im making an iphone game that uses NStimer for movement. I found out that there is a bug that makes the timers stack some times, but i havnt found out what causes is. Is there any way to see how many timers are allocated and is there any way to prevent it by doing something like this:
If(myTimer.numberOfAllocatedTimers == 0) {
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(updateme) userInfo:nil repeats:YES];
}
Try this:
// use this to start timer
myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(doSthing) userInfo:nil repeats:YES];
// use this to stop timer
if (myTimer) {
[myTimer invalidate];
myTimer = nil;
}
A little more context on where this code is being used would be helpful.
However, I'll try to explain why it's happening anyway.
Every time you use [NSTimer scheduledTimerWithTimeInterval...], a new timer is created, and will start repeating. If your code creates a timer inside the callback method, see example below, you'll stack timers.
- (void)updateme {
if ([NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(updateme) userInfo:nil repeats:YES]) {
// This creates stacked timers!
}
}
Here's how you should do it:
#property (nonatomic, strong) NSTimer *myTimer;
...
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(updateme:) userInfo:nil repeats:YES];
...
- (void)updateme:(NSTimer *)timer {
if (timer == self.myTimer) {
// Do something because the timers are equal
}
}

NSTimer does not stop

[self performSelectorInBackground:#selector(method) withObject:nil];
-(void)method
{
timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(getLastImageName1) userInfo:nil repeats:YES];
runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer1 forMode:NSRunLoopCommonModes];
[runLoop run];
}
-(void)viewdidunload
{
[timer1 invalidate];
timer1=nil;
}
I start Timer in HomeViewController even I invalidate, it keeps running in OtherViewController. What is wrong with my code?
First of all, when you're overriding life cycle methods, you should include a call to the super version of that method.
Second of all, Objective-C is case sensitive, so even if your app would try to call the life-cycle even, viewDidUnload, your method would simply never be called because that's what you titled your method.
Third of all, viewDidUnload was deprecated in iOS 6.0 and shouldn't be used at all by this point unless you're going way out of your way to support backward compatibility. It will never be called in iOS 6.0 and greater.
If you want the timer to stop when the user navigates away from the current view, you'll want something like this:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
if (timer1.isValid) {
[timer1 invalidate];
}
timer1 = nil;
}
If you're looking for something else, you'll need to elaborate on what it is you want to accomplish exactly.
If you ARE working on a pre-iOS 6.0 project, for whatever reason, the reason your method isn't being called is at least in part because it is spelled wrong. Again, Objective-C is case sensitive. Your method name should be spelled viewDidUnload.
For future reference, the question shouldn't really be "why isn't my timer invalidating?" You should have start by using breakpoints or NSLog statements to determine whether or not your method, viewdidunload, which tries to invalidate the timer even fires. When you find out it's not being called, do a search to ask "How come viewdidunload isn't called?" Then you'll go fix the capitalization problem and the problem will (probably) remain, so do some more research. And if at the end, you still can't figure it out, as a worst case scenario, the post question should be "how come viewdidunload isn't called?"
timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(getLastImageName1:) userInfo:nil repeats:YES];
set colon for function in selector
-(void) getLastImageName1 :(NSTimer*)timer1
{
//Do your all process and invalidate after completion
[timer1 invalidate];
}
or if you want to remove timer after moving to next view controller use how #nhgrif mentioned
- (void)viewDidDisappear:(BOOL)animated
{
[timer1 invalidate];
}
[self performSelector:#selector(method) withObject:nil afterDelay:0.0];
-(void)method
{
timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(getLastImageName1) userInfo:nil repeats:YES];
runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer1 forMode:NSRunLoopCommonModes];
[runLoop run];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[timer1 invalidate];
timer1=nil;
}
There is no need to add the timer (again) on the main run loop. Or is it necessary for You to run it also in commonModes? For me it was never necessary.
From the NSTimer Documentation:
scheduledTimerWithTimeInterval:invocation:repeats:
Creates and returns a new NSTimer object and schedules it on the
current run loop in the default mode.
Since the NSRunLoop Documentation points out that you can add timer on several run loop modes, maybe this is the problem.
addTimer:forMode:
Discussion You can add a timer to multiple input modes. While running
in the designated mode, the receiver causes the timer to fire on or
after its scheduled fire date. Upon firing, the timer invokes its
associated handler routine, which is a selector on a designated
object.
Also I don't get why you are invoking the timer creation with performSelector?
I just wrote a minimalistic sample. thats totally working!
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(doWork:) userInfo:Nil repeats:YES];
}
- (void)viewDidDisappear:(BOOL)animated{
[self.timer invalidate];
[super viewDidDisappear:animated];
}
- (void) doWork:(id) userInfo
{
NSLog(#"Working again");
}
Hope this helps.
-(void)viewDidUnload is a delegate which fires on memory warning only and is deprecated after iOS 6. It will also never fire on the simulator.
Stop timer in
- (void)viewWillDisappear:(BOOL)animated
or in
- (void)viewDidDisappear:(BOOL)animated

Resources