NSURLSessionDownloadTask and retrying - ios

I am using the following code to download a file from the web. How do i structure the code (using blocks) such that, if it fails, it will be retried with a max of retrycount. Here is the code that i am using to download the file.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *myURL = [NSURL URLWithString:#"urlstring"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL];
[request setValue:token forHTTPHeaderField:#"Authorization"];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *directoryURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
return [directoryURL URLByAppendingPathComponent:fileName];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
//check here if error then retry using block ?
if (!error)
{
//do something with the file
}
else
{
//retry download ??
}
}
}];
[downloadTask resume];

You could create a retryCount instance variable and set it to however many times you want to retry the network call. Then you could put all your networking code in a method with a custom completion handler. Your completion handler could be a block that takes a BOOL as a parameter and returns void. Then, in your networking code, create a local BOOL variable and set it to YES or NO based on whether the network call succeeded or failed. You'll run the completion handler argument within your networking code. Then the block (completion handler) that you send as a parameter to the method can "see" whether the local variable in your method was set to YES or NO. If it was YES, the network call succeeded, so do nothing. If it was NO, the network call failed, so check retryCount--if it's > 0, call the networking method again and decrement retryCount.
UPDATE
This should give you a rough idea...
typedef void (^CustomCompletionHandler)(BOOL); // Create your CustomCompletionHandler type
- (void)getDataAndCallCompletionHandler:(CustomCompletionHandler *)completionHandler {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *myURL = [NSURL URLWithString:#"urlstring"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL];
[request setValue:token forHTTPHeaderField:#"Authorization"];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
BOOL success = NO; // Create local variable to keep track of whether network call succeeded or failed
NSURL *directoryURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
return [directoryURL URLByAppendingPathComponent:fileName];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
//check here if error then retry using block ?
if (!error)
{
success = YES;
//do something with the file
}
else
{
success = NO;
//retry download ??
}
}
// Get main queue and call completionHandler and pass success:
// completionHandler(success)
}];
[downloadTask resume];
}
You would call this method by doing something like...
[self getDataAndCallCompletionHandler:^(BOOL success) {
// If success == true {
// Do whatever you need to
// } else {
// Check retryCount
// If you need to, call getDataAndCallCompletionHandler: again
// }
}];

Related

how to call again same APIs when response is other then status code 200 or error (some time)

i am using APIs call in my iPhone (objective C) app. my APIS call returns Status code 200, but some time it does return 405 response. when i restart my app i do get proper response.
how to call back same API when i get other then 200 response or if i get error...so that i don't have to restart app again.
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *authValue = [NSString stringWithFormat:#"Bearer %#",
[arrTokenData valueForKey:#"Token"]];
//Configure session with common header fields
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"Authorization": authValue};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSString *url = #"http://test.myserver.am/api/mobile/LookUps/getuserdata";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
//Process the data
}
else if
{
// how to call back same API
}
}
else
{
// how to call back same API
}
}];
[task resume];
Thanks in advance
I considered as you created a generic method of network call. If you will get an error or other than 200 response. Just take the request details from your request and call that generic method with that request(URL).
Sample code:
NSURLSessionDataTask *DataTask = [session dataTaskWithRequest:theRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSHTTPURLResponse *serverResponse = (NSHTTPURLResponse*)response;
if (error == nil) {
if (serverResponse.statusCode == 200) {
NSString *theXML = [[NSString alloc] initWithBytes:
[data bytes] length:[data length] encoding:NSUTF8StringEncoding];
NSLog(#"%#", theXML);
} else {
NSLog(#"%#, %#", serverResponse.URL, serverResponse.allHTTPHeaderFields);
//Call the generic network call method with the above details again
}
} else {
NSLog(#"%#", error. localizedDescription);
NSLog(#"%#, %#", serverResponse.URL, serverResponse.allHTTPHeaderFields);
//Call the generic network call method with the above details again
}
}];
[DataTask resume];
Create new thread and execute the failed requests.
Create one method and write your code in that method and call same method on error like below
- (void)callService {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *authValue = [NSString stringWithFormat:#"Bearer %#",
[arrTokenData valueForKey:#"Token"]];
//Configure session with common header fields
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"Authorization": authValue};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSString *url = #"http://test.myserver.am/api/mobile/LookUps/getuserdata";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
//Process the data
}
else
{
// how to call back same API
[self callService];
}
}
else
{
// how to call back same API
[self callService];
}
}];
[task resume];
}

Download one file at a time using afnetworking

I have an array which contains different URLs. I want to download one file with a progress bar, than start the next one and so on.
Here is my code that I have so far;
-(void) func:(NSArray *) arry
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 900;
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSMutableArray * downloadedUrl = [[NSMutableArray alloc] init];
for (NSString * str in arry) {
NSURL *URL = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL (NSURL targetPath, NSURLResponse response) {
NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
return [tmpDirURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse response, NSURL filePath, NSError *error) {
if(error)
{
NSLog(#"File Not Dowloaded %#",error);
}
}];
[downloadTask resume];
}
}
How would you download one file at a time with a progress bar and then remove the url from array?
Declare one global NSMutableArray of file and used that in the function like below.
-(void) downloadFile {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 900;
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:[self.fileArr firstObject]]; //Here self.fileArr is your global mutable array
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL (NSURL targetPath, NSURLResponse response) {
NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
return [tmpDirURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse response, NSURL filePath, NSError *error) {
if(error)
{
NSLog(#"File Not Dowloaded %#",error);
[self downloadFile];
}
else {
[self.fileArr removeObjectAtIndex:0];
if (self.fileArr.count > 0) {
[self downloadFile];
}
}
}];
[downloadTask resume];
}
Now call this function but before that initialize self.fileArr and after that call downloadFile method.
Hope this will help you.
Give limit the queue to one Operation at a time,
For that, Try to adding dependencies between each operation before you queue them.
If you add dependency between two operations say operation1 and operation2 before adding to queue then the operation2 will not start until operation1 is finished or cancelled.
Do it like:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// Make operation2 depend on operation1
[operation2 addDependency:operation1];
[operationQueue addOperations:#[operation1, operation2, operation3] waitUntilFinished:NO];
UPDATE
// Create a http operation
NSURL *url = [NSURL URLWithString:#"http://yoururl"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Print the response body in text
NSLog(#"Response: %#", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
// Add the operation to a queue
// It will start once added
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];
hope it help you..!

Showing spinner using spinKit in iOS while fetching JSON

What I am trying to do is, on a click of a button I am fetching data using AFNetworking pod. What i want is after fetching data I want to display NSLog(#"/n /nAfter fetching"); but it is coming before the json data.
Here is the code I have written in IBAction of Button.
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
//Load the json on another thread
NSURL *url = [NSURL URLWithString:finalURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response,id responseObject, NSError *error) {
if (error) {
NSLog(#"\n Error --> %#",error);
}
else{
jsonDictionary = (NSDictionary*) responseObject;
NSLog(#"/n Data :: %#",jsonDictionary);
}
}];
[dataTask resume];
});
NSLog(#"/n /nAfter fetching");
Please provide a nice way of doing it and do correct me If I have gone wrong with it. Thank you.
As AFURLSessionManager makes the Asynchronous operation. YOu will get the data in completion
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
//Load the json on another thread
[self startSpinner];
NSURL *url = [NSURL URLWithString:finalURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response,id responseObject, NSError *error) {
[self stopSpinner];
if (error) {
NSLog(#"\n Error --> %#",error);
}
else{
NSLog(#"/n /nAfter fetching"); //this is where you receive data
jsonDictionary = (NSDictionary*) responseObject;
NSLog(#"/n Data :: %#",jsonDictionary);
}
}];
[dataTask resume];
});
if you want to make synchronous operation using URLSession below method will help you
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
returningResponse:(__autoreleasing NSURLResponse **)responsePtr
error:(__autoreleasing NSError **)errorPtr {
dispatch_semaphore_t sem;
__block NSData * result;
result = nil;
sem = dispatch_semaphore_create(0);
[[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (errorPtr != NULL) {
*errorPtr = error;
}
if (responsePtr != NULL) {
*responsePtr = response;
}
if (error == nil) {
result = data;
}
dispatch_semaphore_signal(sem);
}] resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return result;
}

Deprecated: 'sendAsynchronousRequest:queue:completionHandler:' in iOS9

I have my own class to make http calls but now in iOS9 this method is deprecated:
[NSURLConnetion sendAsynchronousRequest:queue:completionHandler:]
I'm trying to test the new one
[NSURLSession dataTaskWithRequest:completionHandler:]
but Xcode give an error because it doesn't found this method.
Xcode compiler warning with deprecated line:
'sendAsynchronousRequest:queue:completionHandler:' is deprecated: first deprecated in iOS 9.0 - Use [NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h
Error with new method:
No known class method for selector 'dataTaskWithRequest:completionHandler:'
Method:
-(void)placeGetRequest:(NSString *)action withHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *error))ourBlock {
NSString *url = [NSString stringWithFormat:#"%#/%#", URL_API, action];
NSURL *urlUsers = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:urlUsers];
//[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:ourBlock];
[NSURLSession dataTaskWithRequest:request completionHandler:ourBlock];
}
Any idea?
dataTaskWithRequest:completionHandler: is an instance method, not a class method. You have to either configure a new session or use the shared one:
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:ourBlock] resume];
[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data,NSURLResponse *response,NSError *error)
{
// Block Body
}];
If you are using the AFNetworking library, you can use a session and then set up to support requests in the background. Using this approach I solved two problems:
1) AFNetworking method is not deprecated
2) the request processing will be done even if the application between the state background
I hope it helps to similar problems. This is an alternative.
-(void) pingSomeWebSite {
NSString* url = URL_WEBSITE; // defines your URL to PING
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setHTTPMethod:#"GET"];
[request setURL:[NSURL URLWithString:url]];
request.timeoutInterval = DEFAULT_TIMEOUT; // defines your time in seconds
NSTimeInterval today = [[NSDate date] timeIntervalSince1970];
NSString *identifier = [NSString stringWithFormat:#"%f", today];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
sessionConfig.timeoutIntervalForResource = DEFAULT_TIMEOUT_INTERVAL; // interval max to background
__block AFURLSessionManager *manager = [[AFURLSessionManager alloc]
initWithSessionConfiguration:
sessionConfig];
NSURLSessionTask *checkTask = [manager dataTaskWithRequest:request
completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
NSLog(#"- Ping to - %#", URL_WEBSITE);
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
dispatch_async(dispatch_get_main_queue(), ^(){
[manager invalidateSessionCancelingTasks:YES];
// LOGIC FOR RESPONSE CODE HERE
});
}];
[checkTask resume];
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){ data, response, error in
// Code
}
task.resume()

How to get download progress in AFNetworking 2.0?

I am using AFURLSessionManager to create a new download task:
AFURLSessionManager* manager = ...
NSProgress* p = nil;
NSURLSessionDownloadTask* downloadTask =
[manager downloadTaskWithRequest:request
progress:&p
destination:^NSURL*(NSURL* targetPath, NSURLResponse* response) {...}
completionHandler:^(NSURLResponse* response, NSURL* filePath, NSError* error) {...}
];
[downloadTask resume];
The file gets downloaded fine, however, how do I get progress notifications?
p is always set to nil. I've filed an issue for that.
I've also tried to call setDownloadTaskDidWriteDataBlock on the manager, and I do get progress notifications there but I receive them all grouped together after the file has been downloaded.
Seems like this area is still a bit buggy in AFNetworking 2.0
Any ideas?
You should observe the fractionCompleted property of your NSProgress object using KVO:
NSURL *url = [NSURL URLWithString:#"http://www.hfrmovies.com/TheHobbitDesolationOfSmaug48fps.mp4"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
NSProgress *progress;
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
// …
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
[progress removeObserver:self forKeyPath:#"fractionCompleted" context:NULL];
// …
}];
[downloadTask resume];
[progress addObserver:self
forKeyPath:#"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
Then add the observer method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"fractionCompleted"]) {
NSProgress *progress = (NSProgress *)object;
NSLog(#"Progress… %f", progress.fractionCompleted);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Of course, you should check keyPath and/or object parameters to decide if that's the object/property you want to observe.
You can also use the setDownloadTaskDidWriteDataBlock: method from AFURLSessionManager (from which AFHTTPSessionManager inherits) to set a block for receiving download progress updates.
[session setDownloadTaskDidWriteDataBlock:^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
NSLog(#"Progress… %lld", totalBytesWritten);
}];
This AFNetworking method maps the URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: method from NSURLSessionDownloadDelegate protocol to a more convenient block mechanism.
BTW, Apple's KVO implementation is severely broken. I recommend using a better implementation like the one proposed by Mike Ash with MAKVONotificationCenter. If you are interested in reading why Apple's KVO is broken, read Key-Value Observing Done Right by Mike Ash.
I faced a similar problem, and found a solution.
Check the link below:
http://cocoadocs.org/docsets/AFNetworking/2.0.1/Categories/UIProgressView+AFNetworking.html
#import <AFNetworking/UIKit+AFNetworking.h>
and use the additional method available to your UIProgressView
setProgressWithDownloadProgressOfTask:animated:
How I did it:
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response){
NSURL *documentsDirectoryPath = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) firstObject]];
return [documentsDirectoryPath URLByAppendingPathComponent:[targetPath lastPathComponent]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error){
NSLog(#"File downloaded to: %#", filePath);
}];
[self.progressView setProgressWithDownloadProgressOfTask:downloadTask animated:YES];
[downloadTask resume];
Simple solutions for Swift:
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let sessionManager = AFURLSessionManager(sessionConfiguration: sessionConfiguration)
let request = NSURLRequest(URL: url)
let sessionDownloadTask = sessionManager.downloadTaskWithRequest(request, progress: nil, destination: { (url, response) -> NSURL in
return destinationPath.URLByAppendingPathComponent(fileName) //this is destinationPath for downloaded file
}, completionHandler: { response, url, error in
//do sth when it finishes
})
Now you have 2 options:
Using UIProgressView and setProgressWithDownloadProgressOfTask:
progressView.setProgressWithDownloadProgressOfTask(sessionDownloadTask, animated: true)
Using AFURLSessionManager and setDownloadTaskDidWriteDataBlock:
sessionManager.setDownloadTaskDidWriteDataBlock { session, sessionDownloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
let progress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
//do sth with current progress
}
At the end do not forget about:
sessionDownloadTask.resume()
For Download file with progress status use this code
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://..."];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress)
{
NSLog(#"Progress: %f", downloadProgress.fractionCompleted);
if (progressBlock) {
progressBlock(downloadProgress);
}
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
if (response && successBlock) {
successBlock(response,filePath);
}
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];

Resources