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.)
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 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
I want execute a function all time while my ios App is running.
What is the class where I need write this function, in the delegate?
I'm confused because if I declared this in viewContorller and change to other viewController this break. Or there is a function like
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
that this is running all time?
Thanks!
If you are referring specifically to didUpdateLocations, this is a method that you don't call directly, but rather the OS calls to deliver location updates to you any time it receives them. While it's typically recommended that your location code is handled by some singleton somewhere to consolidate/encapsulate all the location logic, if you create a CLLocationManager and tell it to startUpdatingLocations, that method will be called constantly** by the OS without you having to deal with timers, loops etc.
** When the app is backgrounded the location updates will stop, and when the app comes back to the foreground the location updates will resume without you needing to restart them. These can come as often as once per second, but once again relies on the OS determining the location of the devices and delivering those updates to you.
If you're referring to anything else that is a different answer, but I suspect you're referring directly to location updates.
I suggest to put the function in another file, and use an NSThread
- (void)createThread
{
[NSThread detachNewThreadSelector:#selector(startBackgroundJob) toTarget:self withObject:nil];
}
- (void)startBackgroundJob
{
[self performSelectorOnMainThread:#selector(monitorApp) withObject:nil waitUntilDone:NO];
}
- (void)monitorApp
{
}
My app has a timer which when the app goes in the background pauses.
To still have the right time when the app comes back in the foreground, I set an NSDate called exitDate in the AppDelegate as follows:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.exitDate = [[NSDate alloc] init];
}
and an NSDate called reentryDate as follows:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
self.reentryDate = [[NSDate alloc] init];
}
Then I get the difference of both NSDates and add them to my timer.
This all works fine as long as the app is not terminated in the background.
If it does get terminated the App starts from the first viewController and the timer has stopped.
To fix that problem I use state restoration. Which also works fine.
Even if the app gets terminated, the app starts back at the last viewController with everything I saved beforehand.
The only thing that doesn't seem to get saved in state restoration is my exitDate even though I explicitly asked to do so.
When the app comes back, the exitDate always equals nil.
I assume it has something to do with the time the exitDate gets set which probably is after the method as follows is called:
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.exitDate forKey:#"UnsavedExitDateAppDelegate"];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
self.exitDate = [coder decodeObjectForKey:#"UnsavedExitDateAppDelegate"];
}
Problem is I've tried setting exitDate in -applicationWillTerminate, -applicationDidEnterBackground and -applicationWillResignActive but everytime when the app starts back up, exitDate is nil.
Any ideas?
Convert NSDate to NSString using NSDateFormatter and save this String in NSUserDefaults on 'Did Enter Background'
And on WillEnterForeground get this NSString and using same NSDateFormatter syntax convert it to NSDate.
Or you can save in Plist and retrieve it vice versa.
You can save your date or any data in shared preferences. Data would be persistence even if app is closed in background. Will definitely take off lots of pain from you as compared to your current method. Let me know if you need more explanation ..:)
Save the date to NSUserDefaults. Don't forget to call the synchronize method, even though this is supposed to happen automatically when the app is backgrounded, because you might be adding your date to it slightly after this happens.
How would I implement a RACSignal that would stop publishing when there are no subscribers to it and auto start when there are subscribers?
Here is a scenario:
Let us say I have a currentLocationSignal in the AppDelegate.
My LocationViewController would subscribe to the currentLocationSignal when view loads and unsubscribe (dispose) when view unloads. Since it takes few seconds to get the current location, I would like to always subscribe to the currentLocationSignal when the app opens (and auto unsubscribe after few seconds), so by the time I arrive to LocationViewController I would get an accurate location.
So there can be more then one subscribers to the signal. When the first subscriber listens, it needs to start calling startUpdatingLocation and when there are no subscribers it needs to call stopUpdatingLocation.
Good question! Normally, you'd use RACMulticastConnection for use cases like this, but, because you want the signal to be able to reactivate later, a connection isn't suitable on its own.
The simplest answer is probably to mimic how a connection works, but with the specific behaviors you want. Basically, we'll keep track of how many subscribers there are at any given time, and start/stop updating the location based on that number.
Let's start by adding a locationSubject property. The subject needs to be a RACReplaySubject, because we always want new subscribers to get the most recently sent location immediately. Implementing updates with that subject is easy enough:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[self.locationSubject sendNext:locations.lastObject];
}
Then, we want to implement the signal that tracks and increments/decrements the subscriber count. This works by using a numberOfLocationSubscribers integer property:
- (RACSignal *)currentLocationSignal {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
#synchronized (self) {
if (self.numberOfLocationSubscribers == 0) {
[self.locationManager startUpdatingLocation];
}
++self.numberOfLocationSubscribers;
}
[self.locationSubject subscribe:subscriber];
return [RACDisposable disposableWithBlock:^{
#synchronized (self) {
--self.numberOfLocationSubscribers;
if (self.numberOfLocationSubscribers == 0) {
[self.locationManager stopUpdatingLocation];
}
}
}];
}];
}
In the above code, the +createSignal: block is invoked every time a new subscriber is added to the returned signal. When that happens:
We check to see if the number of subscribers is currently zero. If so, the just-added subscriber is the first one, so we need to enable (or re-enable) location updates.
We hook the subscriber directly up to our locationSubject, so the values from the latter are automatically fed into the former.
Then, at some future time, when the subscription is disposed of, we decrement the count and stop location updates if appropriate.
Now, all that's left is subscribing to the currentLocationSignal on startup, and automatically unsubscribing after a few seconds:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Use a capacity of 1 because we only ever care about the latest
// location.
self.locationSubject = [RACReplaySubject replaySubjectWithCapacity:1];
[[self.currentLocationSignal
takeUntil:[RACSignal interval:3]]
subscribeCompleted:^{
// We don't actually need to do anything here, but we need
// a subscription to keep the location updating going for the
// time specified.
}];
return YES;
}
This subscribes to self.currentLocationSignal immediately, and then automatically disposes of that subscription when the +interval: signal sends its first value.
Interestingly, -[RACMulticastConnection autoconnect] used to behave like -currentLocationSignal above, but that behavior was changed because it makes side effects wildly unpredictable. This use case should be safe, but there are other times (like when making a network request or running a shell command) when automatic reconnection would be horrible.