NSTimer Logic Failing Somewhere - ios

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.

Related

How to properly handle NSTimer calls?

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

Need a global NSTimer variable, or something similar

So, i need to start a timer in the app delegate method applicationDidEnterBackground:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
globalBackgroundTimer = [NSTimer timerWithTimeInterval:30 invocation:nil repeats:NO];
}
the timer is declared like so in app delegate.h:
extern NSTimer * globalBackgroundTimer;
While the timer runs, i receive background location updates (is enabled in plist), in a view controller, and i want to check constantly in locationManager:didUpdateLocations:
For when the timer has expired so i can end the location updates.
... //code omitted
//this is called repeatedly when the app is in the background, and checks whether the global variable is instantiated, and if it has expired.
if(globalBackgroundTimer)
{
NSLog(#"timer alive");
if(!globalBackgroundTimer.isValid)
{
[locationManager stopUpdatingLocation];
NSLog(#"background timer invalid, stopping location updates");
}
}
But i can't make it work (Mach-O-linker error),
however i also read that this approach was ill-advised. So what do you guys suggest?
Figure out a mechanism to store the state of the timer when the app enters the background (NSUserDefaults or Documents sandbox). Then, when the app enters foreground, recalculate the difference using the NSDate information you saved.
(I realize this may already have been what you were doing before you posted this question.)

UISwitch latency between taps caused by Firebase

I have a view with a bunch of UISwitch.
My problem is that when I tap on a switch I need to wait about 10 seconds before being able to tap any other switch of the view.
Here is my code :
-(void) didTapSwitch:(UISwitch *)sender
{
NSLog(#"BEGIN didTapSwitch, %#",sender);
DADudesManager *dudesManager = [DADudesManager getInstance];
DADude *updatedDude = [dudesManager.dudesList objectAtIndex:[[self.spendingDudesTableView indexPathForCell:sender.superview.superview] row]];
DAAccountManager *accountManager = [DAAccountManager getInstance];
[accountManager.accountsOperationQueue addOperationWithBlock:^{
NSLog(#"BACKGROUND OPERATION BEGINS switchDudeBeneficiates, %#",sender);
DASpendingsManager *spendingsManager = [DASpendingsManager getInstance];
[[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{updatedDude.dudeName: [sender isOn] ? #"1" : #"0"}];
NSLog(#"BACKGROUND OPERATION ENDS switchDudeBeneficiates, %#",sender);
}];
NSLog(#"END switchDudeBeneficiates, %#",sender);
}
My spendingObserver is a Firebase object initiated before.
When the code above is executed, the NSLogs show almost instantaneously in the console, the data is updated online at the same time, but the switches don't react to any tap for another 9 to 11 secs.
Of course commenting the line [[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{weakDude.dudeName: [weakSwitch isOn] ? #"1" : #"0"}]; removes the latency, so the problem must come from Firebase, but I have no clue what's going on.
I am probably missing something obvious as I'm pretty new to IOS development !
I can think couple of reasons.
You are sending the PayLoad in the main thread, which is causing the User INterface events to be suspended.
The code you ran, might be linked to other functions in the library you are using, that might be causing the lag.
TRY - >
try putting your code in an NSOperation and execute that. Or use GCD to do work on different thread just not the UI thread which is the main thead.
Step back and simplify. Make your switch code simply log the change in value. NSLog includes a timestamp, so you can tell when the switch events occur.
If do-nothing code responds quickly, as I suspect it will, then add log statements at the beginning and end of your switch action method. That way you can see if there is a delay between the beginning and end of the processing.
You could also run the app in instruments (time profiler) and see where your app is spending time.

iOS bonjour programming - wait for a response

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

SendSynchronousRequest triggers background expiration handler - IOS

this is how I start a background task when application goes background ,
void applicationDidEnterBackground:(UIApplication *)application
{
btId = UIBackgroundTaskInvalid;
UIApplication* cuiApplication = [UIApplication sharedApplication];
void (^backgroundTimeRemainingExtenderHandler)() = ^() {
NSTimeInterval timeRemaining1 = [cuiApplication backgroundTimeRemaining];
if(btId != UIBackgroundTaskInvalid){
[proximityEngine StopEngine];
[cuiApplication endBackgroundTask:btId];
btId = UIBackgroundTaskInvalid;
}
};
btId= [cuiApplication beginBackgroundTaskWithExpirationHandler:backgroundTimeRemainingExtenderHandler];
if(bgmanager != nil){
[bgmanager BeginBackgroundTaskMainLoop];
}
}
My problem is that when my background task calls :
NSURLConnection sendSynchronousRequest
The expiration block is being called even though there is more time remaning , how can I prevent this ?
Regards ,
James
EDIT :
After reading the answer below : I still have 596 seconds left when querying the amount of time left and yet still IOS calls the expiration block handler.
beginBackgroundTaskWithExpirationHandler: is the means by which apps request a little extra background time to do some tidying up as a result of going into the background. However iOS reserves the right to decide how much time it will offer you, if any at all, and to kill your process if you fail to end within the required amount of time.
You don't get to execute in the background indefinitely and you don't get to pick your own time limit. You can query what you've been allocated via backgroundTimeRemaining but that's pretty much the full extent.
Per the documentation the handler is called "shortly before the application’s remaining background time reaches 0". So you should expect backgroundTimeRemaining not quite to be zero.
That being said, if your URL connection hasn't yet completed then you're just meant to note somewhere that it didn't complete and deal with the error next time you come back from the background, usually by trying again. That's what your expiration handler should do, and it needs to do it fast.
The extra time allotted to your app is non-negotiable however.
In my particular case - which I do not quite sure why it behaved the way it behaved , I performed the task on a different thread than the beginbackgroundtask thread , after sendsync returned in that thread the backgroundtask was interrupted by the OS .
When calling sendsync in the beginbackground original thread , it does not occur.
Not sure if its something logical and I did something wrong or an OS bug.

Resources