NStimer I want to call methos in background - objective-c

I want to make calls in background for every 3 min, so I am using Twilio for that. I am able to make calls for every 3 in foreground, but when I built app on iPhone device in background it is not working. After some time fb session is getting logout..
UIApplication *app1 = [UIApplication sharedApplication];
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask1 = [app1 beginBackgroundTaskWithExpirationHandler:^{
[app1 endBackgroundTask:bgTask1];
bgTask1 = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//run function methodRunAfterBackground
timerForPhone = [NSTimer scheduledTimerWithTimeInterval:[string integerValue] target:self selector:#selector(methodForMakingCall) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timerForPhone forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});

After you put the app into background you don't have much control on it. OS can kill it according to resource needs. Unlike Android, you need to wake your app in order to do some logic. There are some workarounds like using location manager's significant change mechanism but still there is not a designated API for this. You need to keep this in mind whenever you are doing some background logic on iOS.

Related

How do I make my App run an NSTimer in the background?

I'm making a benchmark App for test purposes ONLY. I am not intending this to go to the App Store.
What I need is my NSTimer to continue running on the background using a UIBackgroundTaskIdentifier, save data to a Core Data db and finally push the data to a server (I'm using Parse), after a certain time interval, of course.
So basically, I haven´t found any questions which apply to my specific case. I set my NSTimer like so:
UIBackgroundTaskIdentifier bgTask;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.localInterval target:self selector:#selector(updateCoreData:) userInfo:nil repeats:YES];
the method updateCoreData simply calls the Core Data class and makes the necessary insertions.
I've read about VoIP and the Music playing part, but don't know exactly which one would apply best for my case, nor how to implement them.
I figured it out by myself, for anyone else with a similar problem, what I did was to first turn on the location update flag on your Info.plist file. To do this, you must add the Key called "Required Background Modes" on Xcode 4, then as a value, select "App registers for location updates"
I have a timer declared like so in my .h file:
NSTimer *silenceTimer;
#property (nonatomic, retain) NSTimer *silenceTimer;
Then on the .m file, I declared it like so:
UIBackgroundTaskIdentifier bgTask;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
self.silenceTimer = [NSTimer scheduledTimerWithTimeInterval:300 target:self
selector:#selector(startLocationServices) userInfo:nil repeats:YES];
Finally, on the selector method, I made the following:
-(void)startLocationServices {
[locationManager startUpdatingLocation];
[locationManager stopUpdatingLocation];
}
This will create a timer that starts and immediately stops location services after 5 minutes. This will be enough for the app to stay alive indefinately, unless you kill the process.
XCode 6.3.1, for iOS 8.3
There are big diffrences that I've encountered between the apps. behavior while plugged in to your Mac running XCode using debugger and the same apps. behavior while unplugged.
At current iOS version, 8.3, at most your app is allotted is 180 seconds to run in the background for finite long running task with an expiration handler.
No matter what you try, with your phone unplugged your app will be killed by the system in 180 seconds or earlier. I've done a significant amounts of tests and tricks to confirm this. See my post...
My NSTimer Post
Here is a neat way to tell how much time is left for your app to run before termination, keep in mind that you are not guaranteed this time.
NSTimeInterval backgroundTimeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];
if (backgroundTimeRemaining == DBL_MAX)
{
NSLog(#"background time remaining = undetermined");
}
else
{
NSLog(#"background time remaining = %0.2f sec.", backgroundTimeRemaining);
}
Hope this helps your investigation. Cheers!

iOS: Terminating app in background after a certain period of time

I'm trying to implement a passcode lock feature in my app that lets the user choose how much time must go by before the passcode is required for reentry (similar to the passcode functionality of the OS). So for example the user may be able to select that they want the passcode to be required 5, 10, 20 minutes after exiting the app into the background.
I've tried to deal with presenting a passcode view in different ways, but it is often difficult to figure out the best way to present it, and so I had the idea that perhaps it is best to terminate the app after the time is up, and therefore I would only have to present the passcode screen when the app is launched.
Is this possible to do? I had two thoughts about ways to approach this.
1) Have an NSTimer within the app delegate, start it when the app goes into the background, and then when/if the timer reaches the set number of minutes, then terminate the app? I could see a number of things going wrong with this, for example if the OS terminated the app to free up memory sooner than the timer finished. Although that wouldn't be a huge issue.
2) Set an instance of NSDate when the app goes into the background. Then when the app is being launched, see if this date is more than x minutes ago, and present the passcode entry screen depending on that.
I feel like both of these are a little off. I'm inexperienced with Timers, RunLoops, etc, so any advice is appreciated.
Option 2 seems to be a good solution that we have used with success.
Option 2. Use the ApplicationDelegate Lifecycle methods to drive it.
application:didFinishLaunchingWithOptions:
applicationDidBecomeActive:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationWillTerminate:
applicationDidFinishLaunching:
In the applicationWillResignActive method persist the current timestamp to your UserDefaults, and in the applicationWillEnterForeground check this against the current time and if the passcode interval has passed, present your passcode.
(probably best to clear the timestamp when you are active to minimise the chance of false triggering on receiving calls and SMS etc)
Depending on sensitivity you may want to prepare your views before entering foreground to obscure sensitive data, so they do not return in the unlocked state.
you can follow both for better result. for example use option 2 when app active from didFinishLaunchingWithOptions: and option 1 when application enable from
- (void)applicationDidBecomeActive:(UIApplication *)application or - (void)applicationWillEnterForeground:(UIApplication *)application
option 1-Easiest way is to schedule a NSTimer on the background run-loop. I suggest that the following code is implemented on your application delegate, and that you call setupTimer from applicationWillResignActive:.
- (void)applicationWillResignActive:(UIApplication *)application
{
[self performSelectorInBackground:#selector(setupTimerThread) withObject:nil];
}
-(void)setupTimerThread;
{
  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  NSTimer* timer = [NSTimer timerWithTimeInterval:10 * 60 target:self selector:#selector(triggerTimer:) userInfo:nil repeats:YES];
  NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
  [runLoop addTimer:timer forModes:NSRunLoopCommonModes];
  [runLoop run];
  [pool release];
}
-(void)triggerTimer:(NSTimer*)timer;
{
  // Do your stuff
}
in appDelegate .h
UIBackgroundTaskIdentifier bgTask;
in appDelegate .m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication* app = [UIApplication sharedApplication];
// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
// Synchronize the cleanup call on the main thread in case
// the expiration handler is fired at the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});
NSLog(#"app entering background");
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
OR you could run the NSTimer on a background thread by with something like this (I am intentionally leaking the thread object):
-(void)startTimerThread;
{
  NSThread* thread = [[NSThread alloc] initWithTarget:self selector:#selector(setupTimerThread) withObject:nil];
  [thread start];
}
try with this above code. we use both options its works fine for us. good luck

NSTimer's action method is not being called

I have the following code to create an NSTimer which should update a label each time it fires:
.h file
#interface Game : UIViewController
{
NSTimer *updateTimer;
UILabel *testLabel;
int i;
}
-(void)GameUpdate;
#end
.m file
#implementation Game
-(void)GameUpdate
{
i++;
NSString *textToDisplay = [[NSString alloc] initWithFormat:#"Frame: %d", i];
[testLabel setText:textToDisplay];
NSLog(#"Game Updated");
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
}
//other methods (viewDidUnload, init method, etc.)
#end
When I run it, a label appears in the top that says "0" but does not change. It makes me believe I missed something in how the NSTimer is to be setup. What did I miss?
I used breakpoints and (as you can see) logging to see if the method is actually running, rather than some other error.
I was having a similar problem, and it had a different root cause, related to the run loop. It's worth noting that when you schedule the Timer with the code:
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
The timer will get scheduled with the current thread's runLoop. In your case, because you make this call within the viewDidLoad, it is the main thread, so you are are good to go.
However, if you schedule your timer with a thread other than the main thread, it will get scheduled on the runLoop for that thread, and not main. Which is fine, but on auxiliary threads, you are responsible for creating and starting the initial run loop, so if you haven't done that - your callback will never get called.
The solution is to either start the runLoop for your auxiliary thread, or to dispatch your timer start onto the main thread.
to dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
});
To start a runloop:
After creating a thread using your API of choice, call CFRunLoopGetCurrent() to allocate an initial run loop for that thread. Any future calls to CFRunLoopGetCurrent will return the same run loop.
CFRunLoopGetCurrent();
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:#selector(GameUpdate) userInfo:nil repeats:YES];
Your callback must have this signature:
-(void)GameUpdate:(NSTimer *)timer
This is explicitly in the docs. And the #selector() reference when you setup the timer should be #selector(GameUpdate:) (notice the trailing :).
Try that.
Just in case anyone stumbles across this, I want to point out that this:
[[NSString alloc] initWithFormat:#"Frame: %d", i];
Needs memory management.
Safely replace with:
[NSString stringWithFormat:#"Frame: %d", i];
for the same effect but no need for memory management.
P.S. At time of writing I cannot comment on the original post, so I've added this as an answer.
EDIT: As adam waite pointed out below, this isn't really relevant anymore with the widespread usage of ARC.
I have had a little bit different issue with NSTimer - scheduled method call was ignored during UITableView scrolling.
Timer had been started from main thread. Adding timer explicitly to main run loop resolved the problem.
[[NSRunLoop mainRunLoop] addTimer:playbackTimer forMode:NSRunLoopCommonModes];
Solution found here https://stackoverflow.com/a/2716605/1994889
UPD: CADisplayLink fits much better for updating UI.
According official documentation, CADisplayLink is a:
Class representing a timer bound to the display vsync.
And can be easily implemented like:
playbackTimer = [CADisplayLink displayLinkWithTarget:self selector:#selector(updateUI)];
[playbackTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
and removed like
if (playbackTimer) {
[playbackTimer removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
playbackTimer = nil;
}

NSTimer not firing

I have an NSTimer that I init with this code:
testTimer = [[NSTimer alloc] initWithFireDate:[new objectAtIndex:0] interval:0.0 target:self selector:#selector(works:) userInfo:nil repeats:NO];
[new objectAtIndex:0] is an NSDate in the past.
When I start up the app, the timer is getting created, with a fireDate of immediately (since the date is in the past), however it never calls my works method. (-(void)works:(id)sender
)
Anybody know why this is happening?
You will have to add it to the current run loop if you use initWith.. method to create the timer object.
NSRunLoop * theRunLoop = [NSRunLoop currentRunLoop];
[theRunLoop addTimer:testTimer forMode:NSDefaultRunLoopMode];
Or if you would like it set up for you, use the scheduled... methods to create your timer.
I just recently had an issue with NSTimer. In my case I didn't realize that the method scheduledTimerWithTimeInterval is not multi thread safe. Once I moved the timer to the main thread it started working.
I think I had the same problem as Dobler, but my solution was different.
The problem was that the timer was being created and scheduled in a GCD thread in a block within a
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{})
call (actually nested deep, so it wasn't obvious that this was the case).
Using NSTimer's scheduledTimerWithTimeInterval:... placed the timer into an invalid run loop.
The fix was to change to
timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(...) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

Custom RunLoop Mode

What am I doing wrong here? What am I missing?
- (void)scheduleTimer
{
NSTimer *timer = [NSTimer timerWithTimeInterval:0.15
target:self
selector:#selector(wtf:)
userInfo:nil
repeats:NO];
// This works fine
// [[NSRunLoop currentRunLoop] addTimer:timer
// forMode:NSDefaultRunLoopMode];
// This doesn't work at all - how come?
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:#"MyCustomRunLoopMode"];
}
- (void)wtf:(NSTimer *)aTimer
{
NSLog(#"wtf");
}
The documentation for NSRunLoop seems to indicate one can create custom runloop modes. Am I missing something?
(This is on the main thread of a standard GUI application in Mac OS X)
Update: Notice that I mentioned this was on the main thread of a standard application. Therefore, I'm not running the runloop myself. It's all being handled by NSApplication.
Are you running the runloop for that mode? Just adding a timer won't do anything if the runloop never runs in that mode.
Could it be that the currentRunLoop only runs in common modes?
You should also try:
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate
and check things out with:
- (NSString *)currentMode
--Tom