Initiating Background transfer service in background fetch ios - ios

My objective is to send data/image to the server when the app is in background. From iOS7, we can do this using background fetch. But the background fetch only offers 30 sec time limit. The data which I am sending to the server may take longer time since it has more images. While googling I came across Background Transfer Service which offers unlimited time to upload/download data in the background. Is it possible to initiate the background transfer service in the background fetch code? If so how to handle it.

Whenever you want to start your upload/download (in your case during your 30secs of background fetch) execute the following lines:
NSString *downloadURLString = //Your link here;
NSURL* downloadURL = [NSURL URLWithString:downloadURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
// Create a background session
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *identifier = #"com.yourcompany.yourapp";
NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
});
//Init a NSURLSessionDownloadTask with the just-created request and resume it
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request];
[task resume];
});
Also, don't forget to implement those delegate methods:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;
For a detailed sample, have a look at this sample app

Related

How to get a response from NSURLSessionDownloadTask downloadTaskWithRequest

Some background first:
Application is supposed to grab files from AWS S3 server. In order to do that, first step of that process is to go to local server and get the name of the file and some other information from it. After that step we have a complete URLMutableRequest.
NOTE: I am setting up the NSURLSession as a background session:
- (NSURLSession *)backgroundSession
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"identifier"];
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
});
return session;
}
This is the task to download the files from AWS S3 server:
for this task I want to use the delegates to run in background mode.
#property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;
#property (nonatomic, strong) NSURLSession *defaultSession;
self.defaultSession = [self backgroundSession];
self.downloadTask = [self.defaultSession downloadTaskWithRequest:request];
[self.downloadTask resume];
How to I get a RESPONSE form this REQUEST?
Apple documentation says you can't have a block as completionHandler when using a backgroundSessionConfiguration.
In case anyone wondering how to get download response before download is complete, try this: fire off dataTask instead, get the response, then convert dataTask to download if required.
NSURLSessionTask *task = [session dataTaskWithRequest:request];
[task resume];
NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// use response, convert data task to download task
completionHandler(NSURLSessionResponseBecomeDownload);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
// downloadTask converted from dataTask
}
NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// update progress
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
// copy downloaded file from location
}
NSURLSessionDownloadTask has a response property (part of its base class, NSURLSessionTask) that should be set to the response. See here.
You need to implement the NSURLSessionDownloadDelegate protocol in your class (since you specified the sessions delegate as self).
You should check the docs for the available methods, but you're going to implement at least the following :
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

NSURLSessionDownloadTask Delegates not calling didWriteData method

I'm starting to implement a download method using NSURLSession and successfully downloaded different files from multiple request. But now I wanted to add a progress track, however the delegates for download progress is not being triggered.
Here is my code:
NSURLSessionConfiguration *defaultConfigObject = NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue:nil];
NSURLSessionDownloadTask * downloadTask = [defaultSession downloadTaskWithRequest:request completionHandler:^(NSURL * __nullable location,
NSURLResponse * __nullable response, NSError * __nullable error) {
NSData *data = [NSData dataWithContentsOfURL:location];
[[NSFileManager defaultManager] createFileAtPath:docPath contents:data attributes:nil];
if ([[NSFileManager defaultManager] fileExistsAtPath:docPath]) {
NSDictionary *notificationDic = [[NSDictionary alloc] initWithObjectsAndKeys:docPath,#"docPath", item, #"item", nil];
[[NSNotificationCenter defaultCenter] postNotificationName: #"openFile" object:nil userInfo:notificationDic];
}
}];
[downloadTask resume];
I have the NSURLSessionDownloadDelegate on my header file.
I needed to use completion handler to be able to perform different tasks with the file.
Is there a way I can do it?
If you use downloadTaskWithRequest rendition without the completionHandler parameter, then the progress delegate methods will be called. Obviously, you'll have to move the code currently in the completionHandler block into the didFinishDownloadingToURL method. But if you do this, you'll see didWriteData called.
You will have to initiate your download with:
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
And implement the delegate method for your progress:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
And since you need to perform various tasks when finished, you should also implement this delegate method:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location;
Essentially, the completion handler routines are "convenience" routines to quickly perform the task and then when finished, perform the completion handler. But they don't call the other delegate routines.
In my case the problem was I conform my class with URLSessionDelegate instead of URLSessionDownloadDelegate. Even if I was implementing the URLSessionDownloadDelegate methods.

NSURLSession: how to start downloading again, but not resume to download?

I use NSURLSession, and NSURLSessionDownloadTask to download o file. During the downloading time, I kill the application (by tapping on home button, and swipe off the application). I received an error "Cancel" in the following function as I expected:
#property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
....
self.downloadTask = nil;
...
}
the userinfo of the error contains the resume data, in order that later we can continue with downloading. However, I don't want that. What I want is when I re-open the application, it starts downloading again, but not resume to download.
I assign the self.downloadTask = nil, but it does not resolve the problem.
The following function I used to start downloading:
-(void) download:(NSURLRequest*) request {
if (self.downloadTask != nil) return;
self.session = [self sharedBackgroundSession];
self.downloadTask = [self.session downloadTaskWithRequest:request];
[self.downloadTask resume];
}
- (NSURLSession *)sharedBackgroundSession {
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"abs.com.DownloadApp"];
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
});
return session;
}
Note: I am using XCode6, Objective-C

Multiple File Upload using uploadTaskWithRequest fromFile in background

I am trying to upload multiple images using NSURLSession .It works fine when application is running in foreground.When application enter background,uploading process stop after uploading current task.I would like to upload all the files when application is in background. Any help would be greatly appreciated. Here is my code.
//background task configuration
-(void) uploadOneByOne:(NSString *)individualpath{
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:mutableUrlString]];
NSString *requestURL = [NSString stringWithFormat:#"http://myservice/Service.svc/UploadOrdersToDrive?orderFolder=%#",OrderFolderID];
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:requestURL]];
[request setHTTPMethod:#"POST"];
NSURL *filePath =[NSURL fileURLWithPath:individualpath];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:kSessionIdentifier];
defaultSession= [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionUploadTask *uploadTask =
[defaultSession uploadTaskWithRequest:request
fromFile:filePath];
[uploadTask resume];
}
NSURLSession Delegate
receive first request response
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
NSString * str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Received String %#",str);
NSDictionary *jsonResponseData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
//FolderID to create next image in same folder.
OrderFolderID =[jsonResponseData
objectForKey:#"OrderFolderID"];
}
create next request
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
if(error == nil)
{
//Remove DetailFist
[orderDetailarray removeObjectAtIndex:0];
if (orderDetailarray.count >0){
ChosenImages *item = [orderDetailarray objectAtIndex:0];
[self uploadOneByOne:item.path ];
}
}
//Update progress bar
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
//update progress bar
}
Probably you didn't wait enough for next task to start. Depending from different things like WiFi and battery status, user activity etc. your next task queued in background could start in few minutes.. or few hours.
Btw I don't see code related with completionHandler implementation.
Do not forgot to implement
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
in the App delegate and appropriate session delegate.

How can I receive my data in pieces with using NSURLConnection's sendAsynchronousRequest method?

When I send a synchronous request with NSURLConncetion
[NSURLConnection initWithRequest:myRequest delegate:self];
I can receive my downloaded data in pieces with the following method
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.videoData appendData:data];
NSLog(#"APPENDING DATA %#",data);
}
The advantage of this is that I can write my data directly to a file, and limit ram usage when downloading large files.
When I send an asynchronous request, how can I receive my data in pieces? The only place I see the data given back to me is in the completion handler of the request.
[NSURLConnection sendAsynchronousRequest:videoRequest
queue:downloadQueue
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
NSLog(#"All data is given here!");
}];
Is there any solution to this problem? I'm downloading large files in a view controller and want to continue downloading them if the view controller gets dismissed. The problem is that I'm going to use too much memory if I receive all my data at once when downloading large files.
The only method in NSURLConnection which is synchronous is + sendSynchronousRequest:returningResponse:error:
The following methods are all asynchronous
+ connectionWithRequest:delegate:
– initWithRequest:delegate:
– initWithRequest:delegate:startImmediately:
+ sendAsynchronousRequest:queue:completionHandler:
– start
So the code [NSURLConnection initWithRequest:myRequest delegate:self]; itself is an asynchronous, You can use it as it is.
OR
You can make use of NSURLSession for more control
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
self.downloadTask = [self.session downloadTaskWithRequest:request];
[self.downloadTask resume];
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
}

Resources