I am running into a situation where I'm not able to properly handle NSTimer.
In my app, I've an option of user chats (I'm not using XMPP because of a low budget project, but the chat is working through API calls ONLY). I've scheduled a timer at a time interval of 15 seconds. If any new chats available I'll get it and will update chat view.
Here's the working scenario:
As this is a UITabbar based app, a user will come to "Chat" tab.
A User will have a list of persons with whom he can chat.
A User will select any of a user – will push to Chat Screen.
Where all locally saved chats will be visible and an API call will be made for new chats, on success (or error) of API call, a timer will be scheduled to sync chats at a time interval of 15 seconds.
If a user goes back (pops), in viewDidDisappear: method, I'm invalidating the (running) timer.
In my Unit testing, if I'll continuously push & pop to/from Chat screen, there'll be multiple instances of that timer will get scheduled. I suspect, this is WRONG.
I'm not sure what I'm doing is correct or not though I need your help to understand the right and the correct way to get my functionality done. I guess here there's no need of the code for above explanation.
First of all, why are you not exploring option of push notification? Polling server every 15 second is a bad design :-(.
Second, when it comes to NSTimer it is important to start and stop them from the same thread. I would advise you encapsulate your timer start/stop code in below block always ensuring you deal on main thread with your timer.
dispatch_async(dispatch_get_main_queue(), ^{
});
This is the way o usually work with NSTimer. Hope it helps.
#implementation MainViewController{
NSTimer *myTimer
}
- (void)startTimer{
//Prevents multiple timers at the same time.
[myTimer invalidate];
myTimer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(update) userInfo:nil repeats:YES];
}
- (void)update
{
//Stops the timer if the view in not on the screen
if (!(self.isViewLoaded && self.view.window)) {
[myTimer invalidate];
}
}
#end
Related
I've been able to reproduce a defect in our app twice, but most times I fail. So I'm trying to understand what could possibly be going on here and hopefully have some new things to try. Our app times out and logs the user out after 10 minutes using an NSTimer. Every time the screen is touched the timer is reset, this all works great.
When the user backgrounds the app and comes back, the following code gets called:
- (BOOL)sessionShouldTimeOut {
if (self.timeoutManager) {
NSTimeInterval timeIntervalSinceNow = [self.timeoutManager.updateTimer.fireDate timeIntervalSinceDate:[NSDate date]];
if (timeIntervalSinceNow < 0) {
return YES;
} else {
return NO;
}
}
return NO;
}
- (void)timeoutIfSessionShouldTimeOut {
if ([self sessionShouldTimeOut]) {
[self.timeoutManager sendNotificationForTimeout];
}
}
This (I suspect) is the code that's failing. What happens when it fails is the user logs in, hits the home page and locks their phone. After 10+ minutes, they unlock and the app isn't logged out. When they come back, it's the code above that gets executed to log the user out, but in some scenarios it fails - leaving the user still on the homepage when they shouldn't be.
Here's my current theories I'm trying to test:
The timer somehow fires in the background, which then runs the logout routine, but since we're in the background the UI isn't updated but the timer is invalidated (we invalidate the timer after logout) I'm not sure if UI code called from the background will be shown after the app is in the foreground, so this may not be a possibility.
The user actually is coming back a few seconds before the timer fires, then after a few seconds when it should have fired it doesn't since it was backgrounded for 10 minutes. Do timers continue to hit their original fire time if the app goes to the background?
Somehow, while in the background, self.timeoutManager, updateTimer, or fireDate are being released and set to nil, causing the sessionShouldTimeOut method to return NO. Can variables be nilled in the background? What would cause them to if they could be?
The logout routine gets run while the phone is taking a while to actually move to the app, potentially causing the UI updates to not be reflected?
I'm very open to other theories, as you can see a lot of mine are very very edge case since I'm not sure at all what's happening.
I'd appreciate any guidance anyone can offer as to what else I may be able to try, or even any insights into the underworkings of NSTimer or NSRunLoop that may be helpful in this scenario (the documentation on those is terrible for the questions I have)
In AppDelegate.h set
applicationDidEnterBackground:
UIBackgroundTaskIdentifier locationUpdater =[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:locationUpdater];
locationUpdater=UIBackgroundTaskInvalid;
} ];
This tells the os that you still have things going and not to stop it.
I'm trying to do something quite simple : Stopping a parse.com query after a few seconds, with an NSTimer. I've read after some reasearch it's a good "trick" to use.
Here is how I create my timer :
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:6.0
target:self
selector:#selector(stopRetries:)
userInfo:#{#"query":query}
repeats:NO];
Because i'm running this on a background thread (and outside a viewcontroller class), the timer is inside a dispatch_sync(dispatch_get_main_queue());
But whatever I do, I cannot stop the query, because [query cancel] doesn't do anything. I can't pass it in the userInfo of the timer. Breakpoints show it has an address and is "there" but it looks like junk inside.
What can I be doing wrong and what should I be doing instead?
My main goal is to make the parse.com query stop faster than 30 seconds, and warn the user with an alert.
You can Try this [self performSelector:#selector(abc) withObject:nil afterDelay:6.0];
or
You can Invalidate the timer
I have been staring at my code for hours now so I thought I might try coming here for some fresh eyes. I needed to create a timer so I used the code below to do that. The first line is where I create the timer and the second part is my decrementTime method. This is in Objective C for an IOS app. This is my first time posting on StackOverflow (I usually find the answer I am looking for), so please let me know of any unwritten rules that I am not following.
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(decrementTime) userInfo:nil repeats:YES];
- (void)decrementTime{
self.timeLeft--;
}
I'll add here where I invalidate the first timer
-(IBAction)infoClick:(id)sender{
[_timer invalidate];
}
Then here is info message, where I create another timer
- (void)hideInfoMessage {
_secondTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(decrementTime) userInfo:nil repeats:YES];
}
clarification on my code: the order of events starts with my first code block (creating the timer). Then my second code block is called (invalidating the timer). then finally my third code block is called(making a new timer).
I know it is double incrementing because when I run the code I can visually see the timer double incrementing.
It's pretty easy to invoke the code that creates a timer twice. When you do that you actually have 2 timers running concurrently. Each one will decrement your value, so it will get decremented twice per second.
If you create a timer in your viewWillAppear method, for example, then you need to invalidate it in your viewWillDisappear method so you're sure you only have one running.
The same approach applies to other situations where you create a timer. You need to make sure you balance every call that creates a timer with a call that invalidates that timer.
If you use one of the scheduledTimer... methods, you can save a weak pointer to the timer. The run loop will retain it as long as it's running. When you invalidate it, the system run loop will release it and it will be deallocated. When that happens your weak pointer gets zeroed, so you don't even have to test it to see if it's valid/nil in your viewWillAppear method.
EDIT:
You need to instrument your code. In your infoClick method, is the variable _timer nil? What is it's address?
BTW, the target of an NSTimer is supposed to be a method that takes a single parameter, the timer itself. You should change your decrementTime method to look like this:
- (void) decrementTime: (NSTimer *) timer
{
NSLog(#"In method decrementTime, timer = %X", (unsigned long) timer)
self.timeLeft--;
}
Then look at your log and see if your decrementTime method is being called from 2 different timers (I would bet money that it is.)
You might also want to log the address of the timers you get back from your calls to scheduledTimerWithTimeInterval...
I am pretty new to bonjour/networking with ObjC (although well versed in other areas!) I am asking for a bit of advice - I have an iOS app that will run on multiple iPads, in a store. The apps occasionally have to share some data, and the internet isn't always available so a webservice is not an option, hence I decided on using bonjour.
I have setup the Bonjour/NSNetservices and everything is functioning correctly, the ipads basically form an 'ad-hoc network' and connect automatically at app launch, however I am looking for advice for the following situation:
The app normally shares data in the background, without any user intervention - however there is one function where when a button is pressed on one app, data should be returned from another app remotely. The UI then updates when the data has been received from the other device - however if the connection should be lost to the other device, the data will never reach the users device, and the data will not be displayed. I am wanting to implement some form of timout, but unsure how to do this - any suggestions would be much appreciated!
The process flow for this is something like this:
button press on 'dev 1' > 'dev 1' broadcasts 'dev 2 please send data message' > 'dev 2' responds with requested data [timeout required here] > UI is updated if data is received /[if timeout fires, error message is displayed]
So I really just need a timeout for the last section - and I really cannot think of a way to implement it.
I can post code for this if required.
Thanks!
This solution may work if you have the possibility to manually cancel the request.
I believe you can use a simple NSTimer to cancel the request after a wait. I'm sure it's not the best solution, but it will probably work.
Here's how I would do it.
Create your timer and an NSInteger (to store the timer value) in your class :
NSTimer *timer;
NSInteger timerValue;
Call this method with timer = [self startTimer]; when you fire your request :
- (NSTimer*)startTimer {
timerValue = 30;
return [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTicked:) userInfo:nil repeats:YES];
}
Implement the timerTicked: method :
- (void)timerTicked:(NSTimer*)timer {
timerValue --;
if (timerValue <= 0) {
// Cancel the request
// Show an alert
}
}
You can cancel the timer with [timer invalidate]; but remember this will "destroy" your timer, so it won't fire events ever again.
As you've not indicated how you are currently requesting data from your other device, I have had to make some assumptions.
You can use NSURLRequest with a timeout using requestWithURL:cachePolicy:timeoutInterval:
See NSURLRequest documentation
I want to know how we can auto refresh a uitableview by using polling. I have a table which contains messages users sent. So when I load the view i use AFTNetworking to get the JSOn response and display it in the uitableview. Now if the user stays on the screen and we get a new message posted it should automatically refresh the uitableview.So far what I can do it to call the url at certain time intervals. Any solutions to that?
Following Timer will invoke "loadNewData" after each 10 seconds
NSTimer *refreshTimer = [NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:#selector(loadNewData) userInfo:nil repeats:YES];
write your loading code in this method
-(void)loadNewData
{
// write code to fetch Data from Server
}