I have an app in which I have to download a large number of files, from 400 to 900 files that are about 1GB total.
Which is the best approach to accomplish this?
One NSURLSession and all task enqueued in it?
One NSURLSession and enqueue tasks by packages (10 by 10 for example)?
Multiple NSURLSession with different queues?
Actually I have a NSURLSession within all task (one per file) enqueued, but sometimes I get a Lost connection to background transfer service.
Here is my code:
if([[UIDevice currentDevice] isMultitaskingSupported])
{
__block UIBackgroundTaskIdentifier bgTask;
UIApplication *application = [UIApplication sharedApplication];
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *uuidString;
CFUUIDRef uuid = CFUUIDCreate(nil);
uuidString = CFBridgingRelease(CFUUIDCreateString(nil, uuid));
CFRelease(uuid);
// }
NSURLSessionConfiguration *sessionConfiguration;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0"))
{
sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"com.fiveflamesmobile.bakgroundDownload"];
}
else
{
sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.fiveflamesmobile.bakgroundDownload"];
}
sessionConfiguration.HTTPMaximumConnectionsPerHost = 5;
sessionConfiguration.sessionSendsLaunchEvents = YES;
sessionConfiguration.discretionary = YES;
sessionConfiguration.timeoutIntervalForResource = 0; //NO timeout
sessionConfiguration.timeoutIntervalForRequest = 0; //No timeout
sessionConfiguration.networkServiceType = NSURLNetworkServiceTypeBackground;
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
NSLog(#"##### ------- Sesion created succesfully");
// [self batchDownloading];
for (id<FFDownloadFileProtocol> file in self.selectedCatalogProducto.downloadInfo.arrayFiles)
{
[self startDownloadFile:file];
}
NSLog(#"##### ------- Download tasks created successfully ------");
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
}
One NSURLSession - because you only want to handle session based things just once (auth for example).
One NSOperationQueue - with multiple operations running at the same time. (See property operationCount). It might be a little tricky implementing a NSOperation for the first time, but I'm sure this would be your best bet.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperationQueue_class/index.html
http://nshipster.com/nsoperation/
Oh and by the way, this is a highly object oriented approach, which is always nice =)
Related
I have some NSOperations that are started regularly in my application. They should complete even when the the application is put to background. For this, I'm using the beginBackgroundTaskWithExpirationHandler method.
Am I supposed to use the beginBackgroundTaskWithExpirationHandler/ endBackgroundTask: every time I start my task even if the app is not going to background? Or am I supposed the call the begin/end methods only when I detected a UIApplicationDidEnterBackgroundNotification?
Option 1: Use background task every time
/**
* This method is called regularly from a NSTimer
*/
- (void)processData
{
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}];
NSOperation *operation = ...
[self.queue addOperation:operation];
operation.completionBlock = ^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
};
}
Option 2: Use background task only when the application is about to go to background
/**
* This method is called regularly from a NSTimer
*/
- (void)processData
{
NSOperation *operation = ...
[self.queue addOperation:operation];
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"EnterBackgroundFlushTask" expirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
operationBackgroundId = UIBackgroundTaskInvalid;
}];
// wait for all operations to complete and then
// let UIApplication know that we are done
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
}
Answering my own question. From the Apple Docs:
You do not need to wait until your app moves to the background to
designate background tasks. A more useful design is to call the
beginBackgroundTaskWithName:expirationHandler: or
beginBackgroundTaskWithExpirationHandler: method before starting a
task and call the endBackgroundTask: method as soon as you finish. You
can even follow this pattern while your app is executing in the
foreground.
Other Apple API reference:
You should call this method at times where leaving a task unfinished might be detrimental to your app’s user experience.
You can call this method at any point in your app’s execution.
Option2 is correct option.Here is code from Apple document for your reference.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application 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, preferably in chunks.
[self processData];
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
Apple developer Guide
I need to call a web service in every minute and parse the data when app is in background state.
Since the APP uses location service I have enabled background mode for update Location.
I tried calling location update by using a timer background task, but it not working.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"ending background task");
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:60
target:self.locationManager
selector:#selector(startUpdatingLocation)
userInfo:nil
repeats:YES];
}
Is there any way to implement this with less battery consumption.
I referred this link
I'm not getting which solution is better here.
AppDelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : NSObject {
// Instance member of our background task process
UIBackgroundTaskIdentifier bgTask;
}
#end
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Application entered background state.");
// bgTask is instance variable
NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
if ([application backgroundTimeRemaining] > 1.0) {
// Start background service synchronously
[[BackgroundCleanupService getInstance] run];
}
[application endBackgroundTask:self->bgTask];
self->bgTask = UIBackgroundTaskInvalid;
});
}
There are couple key lines in the above implementation:
The first is the line bgTask = [application beginBackgroundTaskWithExpirationHandler..., which requests additional time to run clean up tasks in the background.
The second is the final code block of the delegate method beginning with dispatch_async. It's basically checking whether there's time left to run an operation via the call [application backgroundTimeRemaining]. In this example, I'm looking to run the background service once but alternatively, you can use a loop checking on the backgroundTimeRemaining on each iteration.
The line [[BackgroundCleanupService getInstance] run] will be a call to our singleton service class, which we'll build right now.
With the app delegate ready to trigger our background task, we now need a service class that will communicate with the web server. In the following example, I'm going to a post a fictitious session key and parse a JSON encoded response. Also, I'm using two helpful libraries to make the request and deserialize the returned JSON, specifically JSONKit and ASIHttpRequest.
BackgroundCleanupService.h
#import <Foundation/Foundation.h>
#interface BackgroundCleanupService : NSObject
+ (BackgroundCleanupService *)getInstance;
- (void)run;
#end
BackgroundCleanupService.m
#import "BackgroundCleanupService.h"
#import "JSONKit.h"
#import "ASIHTTPRequest.h"
#implementation BackgroundCleanupService
/*
* The singleton instance. To get an instance, use
* the getInstance function.
*/
static BackgroundCleanupService *instance = NULL;
/**
* Singleton instance.
*/
+(BackgroundCleanupService *)getInstance {
#synchronized(self) {
if (instance == NULL) {
instance = [[self alloc] init];
}
}
return instance;
}
- (void)run {
NSURL* URL = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.example.com/user/%#/endsession", #"SESSIONKEY"]];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:URL];
[request setTimeOutSeconds:20]; // 20 second timeout
// Handle request response
[request setCompletionBlock:^{
NSDictionary *responseDictionary = [[request responseData] objectFromJSONData];
// Assume service succeeded if JSON key "success" returned
if([responseDictionary objectForKey:#"success"]) {
NSLog(#"Session ended");
}
else {
NSLog(#"Error ending session");
}
}];
// Handle request failure
[request setFailedBlock:^{
NSError *error = [request error];
NSLog(#"Service error: %#", error.localizedDescription);
}];
// Start the request synchronously since the background service
// is already running on a background thread
[request startSynchronous];
}
#end
may be helped
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.
I am working on an iOS application in which I am using Restful web service by NSURLConnection, when I call web service and after calling web service press home button then application goes in to background and it is not getting response in background. In my application response should get even when application will be in background state.
So please suggest me any suitable answer for this.
Should I use NSURLSession ?
NSURL *myURL = [NSURL URLWithString:#""]; // set your url here
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *getTask = [session downloadTaskWithURL:myURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// do stuff with the result
}];
// don't forget to start your task
[getTask resume];
Here's a nice tutorial you might want to take:
http://www.raywenderlich.com/51127/nsurlsession-tutorial
Use UIBackgroundTaskIdentifier, something like this
(void)applicationDidEnterBackground:(UIApplication *)application {
//Start Background Service and get data every 10 second
[self runBackgroundTask:10];
}
(void)runBackgroundTask: (int) time{
UIApplication *app;
app = [UIApplication sharedApplication];
//check if application is in background mode
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
//create UIBackgroundTaskIdentifier and create tackground task, which starts after time
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_main_queue(), ^{
NSTimer *refreshTimer = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(doRefresh) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:refreshTimer forMode:NSDefaultRunLoopMode];
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
}
doRefresh - This is method where you give your web service
I'm using NSURLConnection to download content from a server (and I'm working on an iPad application in iOS 5.0).
I wish the NSURLConnection continue downloading even when the iPad goes on standby.
is it possible?
This is my code:
-(void)startDownload {
UIDevice* device = [UIDevice currentDevice];
BOOL backgroundSupported = NO;
if ([device respondsToSelector:#selector(isMultitaskingSupported)])
backgroundSupported = device.multitaskingSupported;
NSLog(#"\n\nbackgroundSupported= %d\n\n",backgroundSupported);
dispatch_async(dispatch_get_main_queue(), ^ {
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:imageURL];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:NO];
[conn scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[conn start];
if (conn) {
NSMutableData *data = [[NSMutableData alloc] init];
self.receivedData = data;
}
else { ... }
}) ;
}
Thanks!
Every app can continue to execute in the background for roughly 10 minutes before it terminates. Only certain apps can continue to execute in the background, such as audio/gps/bluetooth etc related apps. You can find out more at Background Execution and Multitasking (under App States and Multitasking section to the left).
The following code sample is from the app doc and can help you get started so your connection can last up to about 10 minutes -
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you.
// stopped or ending the task outright.
[application 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, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}