As we know there are some limitations for applications which running in background mode.For example, the NSTimer doesn't work. I tried to write a "Timer" like this which can work in background mode.
-(UIBackgroundTaskIdentifier)startTimerWithInterval:(NSTimeInterval)interval run:(void (^)())runBlock complete:(void (^)())completeBlock
{
NSTimeInterval delay_in_seconds = interval;
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, delay_in_seconds * NSEC_PER_SEC);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// ensure the app stays awake long enough to complete the task when switching apps
UIBackgroundTaskIdentifier taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
completeBlock();
}];
NSLog(#"remain task time = %f,taskId = %d",[UIApplication sharedApplication].backgroundTimeRemaining,taskIdentifier);
dispatch_after(delay, queue, ^{
// perform your background tasks here. It's a block, so variables available in the calling method can be referenced here.
runBlock();
// now dispatch a new block on the main thread, to update our UI
dispatch_async(dispatch_get_main_queue(), ^{
completeBlock();
[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});
});
return taskIdentifier;
}
I called this function like this:
-(void)fire
{
self.taskIdentifier = [self startTimerWithInterval:10
run:^{
NSLog(#"timer!");
[self fire];
}
complete:^{
NSLog(#"Finished");
}];
}
This timer works perfect except that there is one problem. The longest period for background task is 10 minute.(Please refer to the NSLog in startTimerWithInterval).
If there any way to make my timer work more than 10 minutes? By the way, my application is a BLE application, I set UIBackgroundModes to bluetooth-central already.
Related
Please take a look at this very simple piece of code:
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 0; i < 100; i++)
{
NSLog(#"LOOP %d", i);
sleep(1);
}
});
If I send my app to the background state it is still running. Yet if I put the execution onto a non main queue like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
for (int i = 0; i < 100; i++)
{
NSLog(#"LOOP %d", i);
sleep(1);
}
});
then the execution is suspended when my app is going to the background. Is it possible to make it run in a background state when dispatched on a non main queue?
I need to mention that I'm running my app with these background modes enabled:
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>voip</string>
</array>
In addition to using performExpiringActivityWithReason, you can use the UIBackgroundTaskIdentifier API:
// Perform the task on a background queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Request the task assertion and save the ID.
self.backgroundTaskID = [[UIApplication sharedApplication]
beginBackgroundTaskWithName: #"Finish Pending Tasks" expirationHandler:^{
// End the task if time expires.
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskID];
self.backgroundTaskID = UIBackgroundTaskInvalid;
}];
// Add your code here.
// End the task assertion.
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskID];
self.backgroundTaskID = UIBackgroundTaskInvalid;
};
The expiration handler is what gets called before the app is killed, but don’t plan on doing computationally expensive tasks using this method since the OS has system-wide time limits which are out of the developer’s control.
If you need to perform specific tasks in the background for certain system events like network fetches, then consider using a Background Mode.
The advantage of using the UIKit API, in this case, is you can query [[UIApplication sharedApplication] backgroundTimeRemaining] within your for loop to perform any last-minute cleanup steps.
Note that if you’re using App Extensions Apple recommend using the NSProcess API, so advice will vary depending on the use case.
Please try using NSProcessInfo performExpiringActivityWithReason API
[[NSProcessInfo processInfo] performExpiringActivityWithReason:#"myReason" usingBlock:^(BOOL expired)
{
// This block is run on a separate (background) thread
// Put your code here...
}
please note this is only a request for some additional CPU time on a process that is being backgrounded ... you cannot run background code indefinitely.
The block will be invoked a 2nd time with expired == YES when you're about to be killed.
I have this code who send data and receive even when the application is in background mode (minimized app):
MyViewController.m
-(void)viewDidAppear:(BOOL)animated{
[self doUpdateEvenAppMinimized];
}
- (void) doUpdate{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundUpdateTask];
[self sendFilesToServer];//Inside this method have a sleep that call every 5 minutes
//The code used in sendFilesToServer is the same in this website https://dcraziee.wordpress.com/2013/05/29/how-to-upload-file-on-server-in-objective-c/
//[self endBackgroundUpdateTask];//This method is forever...so I not need to call this line
});
}
- (void)beginBackgroundUpdateTask{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
The documentation says that the maximum time is 10 minutes, and to remove it I use the concept of Implementing Long-Running Tasks, For that I select my project > capabilities > Background Modes (Turn On) > External accessory communication (Checked).
With these steps, my application will be exempt from the 10 minutes?
Trying to circumvent the rules to run in the background sounds like the wrong approach. Consider using NSURLSession for long running networking operations that are not tied to the lifetime of your app.
I am trying to run a while loop when I push the button, but I can not push the button because the while loop blocks the UI.
Is there a background thread where I can run the while loop and also push the UIButton?
Personally, I'd run a HUD activity indicator over the top of the UI and then run your loop in the background.
//Start the HUD here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Run your loop here
dispatch_async(dispatch_get_main_queue(), ^(void) {
//stop your HUD here
//This is run on the main thread
});
});
Try this
dispatch_async(dispatch_get_main_queue(), ^{
// your code
});
Once it dispatches, you will not have full control over the operation.
If you want to take the control of the operation. Use
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// Background work
}];
Way 1 :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Write your code here
});
Way 2 :
If you use performSelectorInBackground:withObject: to spawn a new thread.
Way 3 :
[NSThread detachNewThreadSelector:#selector(yourMethod:) toTarget:self withObject:nil];
There are many options for you, Grand Central Despatch is a good option, or you could use a NSTimer to trigger an event in the background every x milliseconds which may also work for you.
dispatch_async(dispatch_get_main_queue(), ^{
// your code
});
Or
NSTimer *refreshTimer;
refreshTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(YourMethodHere) userInfo:nil repeats:YES];
You can use dispatch_async for this purpose.You have to run the loop in the background thread, while the UI updates must be done in the main thread.Here is a link
I am trying to run the process in background thread . I want that process to finish in 60 secs and start running again.No matter application is in foreground or background. I don't know how to implement and where to implement it.I'm using ios7.In that process I'm also taking location updates.
I read about the background tasks, but it wasn't giving proper idea of the process. Can someone provide me with good source or link?
There is no such api given by ios for background process unlike android which use service for that.You can use timer for continuos background process .Also there is dispatch_async ,selector in background for efficient background processing.
Hope this helps.
you can use something like this for background processing,but remember apple has put restriction of 10-15 min to complete the processing.
UIApplication* app = [UIApplication sharedApplication];
task = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:task];
task = 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.
NSLog(#"Started background task timeremaining = %f", [app backgroundTimeRemaining]);
if (connectedToNetwork) {
// do work son...
}
[app endBackgroundTask:task];
task = UIBackgroundTaskInvalid;
});
Also you can check the following :
**BOOL backgroundSupported = NO;
if ([device respondsToSelector:#selector(isMultitaskingSupported)])
backgroundSupported = device.multitaskingSupported;**
I am using a serial dispatch queue to serialize some network requests when the user moves the app to the background.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
dispatch_queue_t opQ = dispatch_queue_create("com.myapp.network", NULL);
dispatch_async(opQ, ^{
[self sendNetworkData1];
[self sendNetworkData2];
[self sendNetworkData3];
});
}
The problem is that when they run on this queue I have created, the app doesn't stay active even for the 5 seconds it is supposed to.
On the contrary, when I send the same requests outside of a queue, they are being sent for approximately 8 sec. but the app crashes afterwards.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self sendNetworkData1];
[self sendNetworkData2];
[self sendNetworkData3];
}
I would also like to write the remaining ones on the disk, so that they can be sent the next time the user opens the app.
What's the best way to implement this?
When the application enters the background if it requires additional time to complete some task you will want to notify the OS of that. The detailed documentation is here: http://developer.apple.com/library/ios/#DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html. Here's a quick and dirty patch.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
__block UIBackgroundTaskIdentifier backgroundTask; //Create a task object
backgroundTask = [application beginBackgroundTaskWithExpirationHandler: ^ {
[application endBackgroundTask:background_task];
backgroundTask = UIBackgroundTaskInvalid; //Set the task to be invalid
}];
dispatch_queue_t opQ = dispatch_queue_create("com.myapp.network", NULL);
dispatch_async(opQ, ^{
[self sendNetworkData1];
[self sendNetworkData2];
[self sendNetworkData3];
[application endBackgroundTask:background_task];
backgroundTask = UIBackgroundTaskInvalid; //Set the task to be invalid
});
}
The bottom line is that you notify that the application needs to run in the background with beginBackgroundTaskWithExpirationHandler: THen when your done you call endBackgroundTask: to notify the OS that you are finished processing in the background. And finally make sure that you reset the backgroundTask variable to UIBackgroundTaskInvalid.