The default background fetch interval is 3 min in iOS 7 or later but I want to background fetch after a 2 hours for news update.
I tried too many methods like i.e.
Use the NSTimer
Sleep the thread
Set minimum background fetch internal
After all hard work the result is
After Some time application crash
After Some time when application come in foreground then news update
After Some time no new data update
I have tried this code:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
__block UIBackgroundTaskIdentifier taskID=[[UIApplication sharedApplication]beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"background task expiration handler called");
NSLog(#"Task ID is:%lu",(unsigned long)taskID);
[[UIApplication sharedApplication]endBackgroundTask:taskID];
}];
if(taskID==UIBackgroundTaskInvalid)
{
NSLog(#"Failed to start background task");
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Start Background task with %f seconds remaining",[[UIApplication sharedApplication]backgroundTimeRemaining ]);
[self download];
[NSThread sleepForTimeInterval:25];
NSLog(#"Finishing Background task with %f seconds remaining",[[UIApplication sharedApplication]backgroundTimeRemaining ]);
[[UIApplication sharedApplication]endBackgroundTask:taskID];
});
}
-(void)download
{
ViewController *viewController = (ViewController *)self.window.rootViewController;
NSDate *fetchStart=[NSDate date];
[viewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
viewController.i++;
if(viewController.i<5)
{
[self download];
}
NSLog(#"%lu",(unsigned long)result);
}];
NSDate *fetchEnd = [NSDate date];
NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
NSLog(#"Background Fetch Duration: %f seconds", timeElapsed);
}
I used the recursion to update five news channels. My device not
connected to X-Code. My background fetch is on in capabilities.
Related
I need to run some code when the user enters a background state. The default time I was getting for when I entered the background on iOS 9 was 10 seconds. I needed a bit more than that, so I found that this code will extend the time to 3 minutes:
- (void)extendBackgroundRunningTime {
if (_backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return;
}
NSLog(#"Attempting to extend background running time");
__block Boolean self_terminate = YES;
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"DummyTask" expirationHandler:^{
NSLog(#"Background task expired by iOS");
if (self_terminate) {
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Background task started");
while (true) {
NSLog(#"background time remaining: %8.2f", [UIApplication sharedApplication].backgroundTimeRemaining);
[NSThread sleepForTimeInterval:1];
}
});
}
However, my task doesn't need all of this extra time, and I would like to conserve as much battery as possible. Is there any way to use this or similar code to get 1 minute of background time, or some other value between 10 and 180 seconds?
You should call endBackgroundTask: once you are done with your background processing. If you are done before the 3 minute time allotted to you, that should end your background processing early and let iOS suspend you. I haven't tested it to verify, but that's what the docs suggest.
I have a app that fetch some content from server via REST api after every 5 mins in background using the UIBackgroundTaskIdentifier. My problem is that this works fine for 1,2 hours and then after it is expired it never re starts the background task. The code I am using is given below,
In AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIApplication* app = [UIApplication sharedApplication];
self.expirationHandler = ^{
[app endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
// self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
NSLog(#"Expired");
self.jobExpired = YES;
while(self.jobExpired) {
// spin while we wait for the task to actually end.
[NSThread sleepForTimeInterval:1];
}
self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
// Restart the background task so we can run forever.
[self startBackgroundTask];
};
self.bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
[self monitorBatteryStateInBackground];
}
- (void)monitorBatteryStateInBackground
{
NSLog(#"Monitoring update");
self.background = YES;
[self startBackgroundTask];
}
- (void)startBackgroundTask
{
NSLog(#"Restarting task");
// Start the long-running task.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// When the job expires it still keeps running since we never exited it. Thus have the expiration handler
// set a flag that the job expired and use that to exit the while loop and end the task.
while(self.background && !self.jobExpired)
{
[self uploadPhotostoServer];
[NSThread sleepForTimeInterval:240.0];
}
self.jobExpired = NO;
});
}
In expired section it do come but never calls the method [self startBackgroundTask]
Any help will be much appreciated.
So, I am trying to create a small project that tells user how many followers he has.
Currently I'm using Instagram Kit, which uses asynchronous blocks.
My aim is to make program update it's data in the background fetch and change the badge value next to the icon to the number of followers. I had some troubles with the background fetch cause the block that gets the information from instagram completes after the fetch, so no data is retrieved.
I read about semaphores and decided to use them. After i added some code to the method that is called in background fetch, it does the following:
the fetch starts (I get the "We begin" NSLog), then the main thread is freezed by semaphore. It should be resumed by the completition of the "withSuccess"/"failure" part of block. However it gets sucked. And nothing else happens. I am puzzled.
What should I do?
(below is the method that is called during the fetch, I store the instagram-token locally, so no need to request it every time)
-(IBAction) backgroundActions {
NSLog(#"We begin");
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *user) {
NSLog(#"%d",user.followedByCount);
[UIApplication sharedApplication].applicationIconBadgeNumber = user.followedByCount;
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
NSDate *now = [NSDate date];
localNotification.fireDate = now;
localNotification.alertBody = [NSString stringWithFormat:#"You have %d followers.", user.followedByCount];
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber = user.followedByCount;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
dispatch_semaphore_signal(sem);
} failure:^(NSError *error) {
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
What you've done is make the asynchronous getSelfUserDetailsWithSuccess become synchronous. That is why your main thread is freezing.
Otherwise the code looks fine. What's wrong with removing the semaphore and letting the update happen asynchronously?
I am writing an iOS app for scanning barcodes at participating retail locations, where that location would donate to charity on the customer's behalf after the customer scans a QRcode printed on the receipt.
I would like to send a local notification to a user if they are in a participating location for 60 seconds or longer, reminding them to scan any receipts they might get from purchases they made there.
My issue is that I would like to delay a call for 60 seconds when a user enters a region - if after those 60 seconds they are still in that region fire off the local notification - however, sometimes that call to sendLocalNotification within stillInRegion doesn't fire until the app returns to the foreground. I believe this has to do with the thread sometimes ending before the delay is up, but I am not sure. I have tried about every approach I could find on stackoverflow and elsewhere (blocks, nstimers, etc.) but to no avail. Any ideas about how to better approach this problem?
- (void) sendLocalNotification:(NSString *)regionId {
NSLog(#"we entered %# and we're currently in %#", regionId, self.currentRegionId);
if ([regionId isEqualToString:self.currentRegionId]) {// if we're still in the region, send a local notification
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil) return;
NSDate *fireTime = [[NSDate date] addTimeInterval:2]; // adds 2 secs
localNotif.fireDate = fireTime;
localNotif.alertBody = [NSString stringWithFormat:#"Did you just visit %#? If so, don't forget to scan your receipt!", regionId];
localNotif.applicationIconBadgeNumber = [UIApplication sharedApplication].applicationIconBadgeNumber+1;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
[localNotif release];
}
}
- (void) stillInRegion:(CLRegion *)region {
NSLog(#"did enter region: %#", region.identifier);
[self performSelector:#selector(sendLocalNotification:) withObject:region.identifier afterDelay:60];
}
- (void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
if (self.didLaunchForRegionUpdate) {
NSString *path = [DGGeofencingHelper applicationDocumentsDirectory];
NSString *finalPath = [path stringByAppendingPathComponent:#"notifications.dg"];
NSMutableArray *updates = [NSMutableArray arrayWithContentsOfFile:finalPath];
if (!updates) {
updates = [NSMutableArray array];
}
NSMutableDictionary *update = [NSMutableDictionary dictionary];
[update setObject:region.identifier forKey:#"fid"];
[update setObject:[NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]] forKey:#"timestamp"];
[update setObject:#"enter" forKey:#"status"];
[updates addObject:update];
[updates writeToFile:finalPath atomically:YES];
} else {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:#"enter" forKey:#"status"];
[dict setObject:region.identifier forKey:#"fid"];
NSString *jsStatement = [NSString stringWithFormat:#"DGGeofencing.regionMonitorUpdate(%#);", [dict JSONString]];
[self.webView stringByEvaluatingJavaScriptFromString:jsStatement];
}
self.currentRegionId = region.identifier;
self.cRegionEnterTime =[NSDate date];
[self stillInRegion:region];
}
Are the locationManager:didEnterRegion being called in foreground and then your application goes to background?
I'm not very clear about when you are calling those methods, but you could try creating a background task as follows:
Add a property of type NSUInteger called, for example, bgTaskIdentifier, to store your background task identifier.
Before your call to [self stillInRegion:region]; add the following code:
bgTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{}];
It should call your stillInRegion method, and continues even if you go to the background, so the delay should continue counting on!. Finally, you should end your background task. To do this, add the following line at the end of your sendLocalNotification method, after the if block:
[[UIApplication sharedApplication] endBackgroundTask:bgTaskIdentifier];
Just let us know if that was helpful! and excuse me for my poor english!
Have a good day!
inside my didEnterRegion method I used to following code to achieve the desired result:
UIApplication* app = [UIApplication sharedApplication];
bgTaskIdentifier = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTaskIdentifier];
bgTaskIdentifier = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:180 target:self selector:#selector(stillInRegion:) userInfo:region.identifier repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
Pretty close to what #lucaslt89 had but a slight variation on it.
I've an app which run in background, and I want to stop all location services 30 min after entering into background mode.
So in my background function, I do this :
// 1800 sec = 30 min * 60 sec.
NSDate *date30min = [[NSDate alloc] initWithTimeIntervalSinceNow:1800.0];
NSLog(#"Date30min : %#", date30min);
self.timer = [[NSTimer alloc] initWithFireDate:date30min interval:1 target:self selector:#selector(stopLocation) userInfo:nil repeats:NO];
And my stopLocation function is :
- (void)stopLocation
{
NSLog(#"[My APP] [PASS INTO stopLocation]");
[self.locationManager stopMonitoringSignificantLocationChanges];
[self.locationManager stopUpdatingLocation];
[self.locationManager stopUpdatingHeading];
}
But my timer never call the function, what's my error please ?
(My function is correctly implement into my .h and .m file, I tested this out of the background function.
Please help..
It's not a problem with forgetting to add your timer to a run loop (in this case).
NSTimer objects don't fire when your app goes into the background. So, I would use another technique if you want to do something 30 minutes after backgrounding.
For example, use a Background Task to run your stopLocation method later:
- (void)applicationDidEnterBackground:(UIApplication *)application {
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
NSLog(#" expiration handler!");
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 30 * 60 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
[self stopLocation];
// we're done with this task now
[application endBackgroundTask: bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
Of course, you also need to have declared this:
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
in your Info.plist file, but I assume you already figured that out.