I want to download multiple file and then display a total progress to the user.
but the problem is here I dont know how should I calculate total progress .
here is what I do:
first I get to totalBytes Expected to receive from all of the files:
for (NSURL candidateUrl in UrlsList)
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:candidateURL];
//setting HTTPMethod of request from GET to HEAD to download header files of requests, so we can get file size before downloaing file
[request setHTTPMethod:#"HEAD"];
getTotalImagesBytesOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[getTotalImagesBytesOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//setting totalImagesBytesExpectedToRead of all images. we use it to know how many bytes we should download for all the images
totalImagesBytesExpectedToRead += [operation.response expectedContentLength];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operationQueue addOperation:getTotalImagesBytesOperation];
}
after estimating total files size:
//downloading images
for (NSURL *imageUrl in imagesURLList) {
NSURLRequest *request = [NSURLRequest requestWithURL:imageUrl];
AFImageRequestOperation *downloadImageOperation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success: ^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(#"success")
}
failure: ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"%#", [error localizedDescription]);
}];
[operationQueue addOperation:downloadImageOperation];
[downloadImageOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
**HOW TO CALCULATE TOTAL PROGRESS**
}
I don't know how to calculate total size!!
the values we have:
totalBytesOfAllTheFiles, totalBytesRead and totalBytesExpectedToRead for the current file which above method gives you, indexOfCurrentFile and countOfFiles.
be cautious setDownloadProgressBlock called hundreds a time.
does anyone have any idea? (sorry for bad formatting of code!)
Here is how I achieved that:
first create an NSOperationQueue:
// Add the operation to a queue
// It will start once added
//calculating images byte size
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:1];
foreach (NSURL *candidateURL in urlList )
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:candidateURL];
//setting HTTPMethod of request from GET to HEAD to download header files of requests, so we can get file size before downloaing file
[request setHTTPMethod:#"HEAD"];
AFHTTPRequestOperation *getTotalImagesBytesOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[getTotalImagesBytesOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//setting totalImagesBytesExpectedToRead of all images. we use it to know how many bytes we should download for all the images
totalImagesBytesExpectedToRead += [operation.response expectedContentLength];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operationQueue addOperation:getTotalImagesBytesOperation];
}
//downloading images which should be downloaded
for (NSURL *imageUrl in imagesShouldBeDownlaoded) {
NSURLRequest *request = [NSURLRequest requestWithURL:imageUrl];
AFHTTPRequestOperation *downloadFileOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
//we must provide file path with file name and its extension
NSString *fileName = [self getImageName:[imageUrl absoluteString]];
downloadFileOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:[[ImageManager applicationDocumentsDirectory] stringByAppendingPathComponent:fileName] append:NO];
[downloadFileOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation , id response)
{
NSLog(#"file saved");
}failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
completionBlock(NO, error);
NSLog(#"%#", [error localizedDescription]);
}];
[operationQueue addOperation:downloadFileOperation];
[downloadFileOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
totalBytesDownloaded += bytesRead;
//total progress from 0.0 to 1.0
progress = ((float)totalBytesDownloaded/(float)totalImagesBytesExpectedToRead);
if(progress == 1)
{
completionBlock(YES, nil);
}
progressCallback(progress);
}];
operationQueue works as a FIFO list, first calculate images byte size, then starts to download all images
AFNetworking3 use dispatch_group and NSProgress to achieved this:
#import <Foundation/Foundation.h>
#import <AFNetworking.h>
#interface Server : NSObject
#property (nonatomic, strong) AFHTTPSessionManager *manager;
#property (nonatomic, strong) NSProgress *progress;
- (void)downloadFilesWithUrls:(NSMutableArray <NSURL *>*)urls;
#end
#import "Server.h"
#implementation Server
- (instancetype)init
{
self = [super init];
if (self) {
_manager = [AFHTTPSessionManager manager];
_manager.requestSerializer = [AFJSONRequestSerializer serializer];
_manager.responseSerializer = [AFJSONResponseSerializer serializer];
_progress = [NSProgress progressWithTotalUnitCount:0];
[_progress addObserver:self forKeyPath:#"fractionCompleted" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(#"fractionCompleted:%lf",self.progress.fractionCompleted);
}
- (void)downloadFilesWithUrls:(NSMutableArray <NSURL *>*)urls {
//progress
dispatch_group_t group = dispatch_group_create();
_progress.totalUnitCount = urls.count;
[urls enumerateObjectsUsingBlock:^(NSURL * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_group_enter(group);
NSURLRequest *request = [NSURLRequest requestWithURL:obj];
NSURLSessionDownloadTask *task = [self.manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil] URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
NSLog(#"one task completion");
dispatch_group_leave(group);
}];
NSProgress *child = [self.manager downloadProgressForTask:task];
[self.progress addChild:child withPendingUnitCount:1];
[task resume];
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"%lf", self.progress.fractionCompleted);
});
}
#end
Related
Im trying to upload image to my server by using AFNetworking 3.0. My server returns "Please select a file to upload.". Here is how i catch the uploaded file in my php file $filename = $_FILES["file"]["name"];.
-(void)uplodeImages :(NSString *)image {
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:#"http://local/upload.php" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:[NSURL fileURLWithPath:image] name:#"file" fileName:#"imagename.jpg" mimeType:#"image/jpeg" error:nil];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionUploadTask *uploadTask;
uploadTask = [manager uploadTaskWithStreamedRequest:request progress:^(NSProgress * _Nonnull uploadProgress) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Laddar...");
});
} completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if (error) {
NSLog(#"Error: %#", error);
} else {
NSLog(#"%# %#", response, responseObject);
}
}];
[uploadTask resume];
}
Note :- I have just implemented Image Upload service using AFNetworking 3.0,
-> Here kBaseURL means Base URL of server
->serviceName means Name of Service.
->dictParams means give parameters if needed, otherwise nil.
->image means pass your image.
Note :- This code written in NSObject Class and we have apply in our Project.
+ (void)requestPostUrlWithImage: (NSString *)serviceName parameters:(NSDictionary *)dictParams image:(UIImage *)image success:(void (^)(NSDictionary *responce))success failure:(void (^)(NSError *error))failure {
NSString *strService = [NSString stringWithFormat:#"%#%#",kBaseURL,serviceName];
[SVProgressHUD show];
NSData *fileData = image?UIImageJPEGRepresentation(image, 0.5):nil;
NSError *error;
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:strService parameters:dictParams constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
if(fileData){
[formData appendPartWithFileData:fileData
name:#"image"
fileName:#"img.jpeg"
mimeType:#"multipart/form-data"];
}
} error:&error];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionUploadTask *uploadTask;
uploadTask = [manager uploadTaskWithStreamedRequest:request progress:^(NSProgress * _Nonnull uploadProgress) {
NSLog(#"Wrote %f", uploadProgress.fractionCompleted);
}
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
[SVProgressHUD dismiss];
if (error)
{
failure(error);
}
else
{
NSLog(#"POST Response : %#",responseObject);
success(responseObject);
}
}];
[uploadTask resume];
}
--> Now apply in our project.
UIImage *imgProfile = //Pass your image.
[WebService requestPostUrlWithImage:#"save-family-member" parameters:nil image:imgProfile success:^(NSDictionary *responce) {
NSString *check = [NSString stringWithFormat:#"%#",[responce objectForKey:#"status"]];
if ([check isEqualToString: #"1"]) {
//Image Uploaded.
}
else
{
//Failed to Upload.
}
} failure:^(NSError *error) {
//Error
}];
When I use AFNetworking 2, I could get progress with AFHTTPRequestOperation like this:
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:aURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:_timeoutInterval];
AFHTTPRequestOperation *imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
imageRequestOperation.responseSerializer = [AFImageResponseSerializer serializer];
__weak AFHTTPRequestOperation *imageRequestOperationForBlock = imageRequestOperation;
[imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// ...
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// ...
}];
[imageRequestOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
if (progress) {
progress((float)totalBytesRead / totalBytesExpectedToRead);
}
}];
I use AFHTTPSessionManager GET:parameters:success:failure: to get data, but I don't know how to get download progress in AFNetworking 3.0.
Link in comments was missleading (NSURLSessionDownloadTask). Sorry about that.
Code below should work though.
Assumption: this code is placed in AFHTTPSessionManager subclass with an NSURLSessionDataTask *testTask ivar declared. It should be easy enough modify as needed.
Vital part of code taken from this answer
- (void)test
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://eoimages.gsfc.nasa.gov/images/imagerecords/78000/78797/nwpassage_tmo_2012199_lrg.jpg"]];
testTask = [self dataTaskWithRequest:request
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error)
{
//...
NSLog(#"error");
}
else
{
//...
UIImage *result = responseObject;
NSLog(#"Success: %#",NSStringFromCGSize(result.size));
}
}];
[self setDataTaskDidReceiveDataBlock:^(NSURLSession *session,
NSURLSessionDataTask *dataTask,
NSData *data)
{
if (dataTask.countOfBytesExpectedToReceive == NSURLSessionTransferSizeUnknown)
return;
if (dataTask != testTask)
return;
NSUInteger code = [(NSHTTPURLResponse *)dataTask.response statusCode];
if (!(code> 199 && code < 400))
return;
long long bytesReceived = [dataTask countOfBytesReceived];
long long bytesTotal = [dataTask countOfBytesExpectedToReceive];
NSLog(#"... %lld/%lld",
bytesReceived,
bytesTotal);
}];
[testTask resume];
}
I have this method here:
-(BOOL)User:(NSString *)user andPassWordExists:(NSString *)password
{
__block BOOL isLoggedIn = YES;
NSURL *url = [NSURL URLWithString:kIP];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCredential:[NSURLCredential credentialWithUser:[#"domain" stringByAppendingString:user]
password:password persistence:NSURLCredentialPersistenceForSession]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
isLoggedIn = YES;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
isLoggedIn = NO;
}];
[operation start];
return isLoggedIn;
}
I get no errors, but when I put brake points on success and failure and neither one of them are getting hit. I know the URL is correct and I can see the creds being passed in, I dont know what the issue is ?
UPDATE
I have changed my code to this:
-(void)User:(NSString *)user andPassWordExists:(NSString *)password completionHandler:(void (^)(NSArray *resultsObject, NSError *error))completionHandler
{
NSURL *url = [NSURL URLWithString:kIP];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCredential:[NSURLCredential credentialWithUser:[#"domain" stringByAppendingString:user]
password:password persistence:NSURLCredentialPersistenceForSession]];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[[NSOperationQueue mainQueue] addOperation:operation];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[operation start];
}
Now I need to add this is my code:
[self fetchDataWithCompletionHandler:^(id responseObject, NSError *error) {
if (responseObject) {
}
}];
but when I do, I get this error:
Use of undeclared identifier 'self'
I guess this where I am confused because this how I would call this method from another method:
- (void)Login
{
NSString *rawString = [self.idTextField text];
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
[self.idTextField setText:[rawString stringByTrimmingCharactersInSet:whitespace]];
BOOL *isAuthenticated = [userName User:self.idTextField.text andPassWordExists:self.passwordTextField.text];
if(isAuthenticated != 0){
[self.idTextField removeFromSuperview];
[self.passwordTextField removeFromSuperview];
[self.loginButton removeFromSuperview];
self.idTextField = nil;
self.passwordTextField = nil;
self.loginButton = nil;
[self CreateMenu];
}else{
[self CustomAlert:#"Sorry Login Failed, User and/or Passsword Incorrect"];
}
[indicatorView stopAnimating];
[indicatorView removeFromSuperview];
indicatorView = nil;
[loadingView removeFromSuperview];
loadingView = nil;
}
So inside this:
[self fetchDataWithCompletionHandler:^(id responseObject, NSError *error) {
if (responseObject) {
}
}];
would I want to either return true or false and my Login method would still work?
UPDATE
I have updated my method to this:
-(void)User:(NSString *)user andPassWordExists:(NSString *)password completionHandler:(void (^)(NSArray *resultsObject, NSError *error))completionHandler
{
NSURL *url = [NSURL URLWithString:kIP];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCredential:[NSURLCredential credentialWithUser:[#"domain" stringByAppendingString:user]
password:password persistence:NSURLCredentialPersistenceForSession]];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[[NSOperationQueue mainQueue] addOperation:operation];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(nil, error);
}
}];
[operation start];
[self User:^(id responseObject, NSError *error) {
if (responseObject) {
return true;
}
}];
}
Two things wrong here....
I get an error at the end of my fetchDataWithCompletionHandler code:
/Users/jsuske/Documents/SSiPad(Device Only)ios7/SchedulingiPadApplication/Classes/LHJSonData.m:237:5: Control may reach end of non-void block
and I get this warning:
/Users/jsuske/Documents/SSiPad(Device Only)ios7/SchedulingiPadApplication/Classes/LHJSonData.m:233:11: Instance method '-fetchDataWithCompletionHandler:' not found (return type defaults to 'id')
Am I getting close?
This is an excellent start. I might suggest
changing the closure to simply be BOOL;
eliminate starting the operation until you're done configuring everything (and either call start or add it to a queue, but not both); and
eliminate the call to user at the end.
Thus
- (void)verifyUserid:(NSString *)user password:(NSString *)password completionHandler:(void (^)(BOOL success, NSError *error))completionHandler
{
NSURL *url = [NSURL URLWithString:kIP];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCredential:[NSURLCredential credentialWithUser:[#"domain" stringByAppendingString:user]
password:password persistence:NSURLCredentialPersistenceForSession]];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(TRUE, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(FALSE, error);
}
}];
[operation start];
}
I've also changed the method name to conform to Cocoa conventions (start with verb, method names always begin with lowercase letter), but that's more a stylistic matter than anything else.
Now you can call this method with something like:
[self verifyUserid:user password:password completionHandler:^(BOOL success, NSError* error) {
if (success) {
// userid/password ok; do anything related to successful login here
} else {
// not ok
}
}];
// note, we're not logged in yet (the above completion block is called later),
// so don't assume we're logged in or not here
I am to execute a chain of request using NSURLSessionDataTask. When first request finished, I need to use the responseData from the fist request to execute another multiple request. and In the end, I get the NSArray and give to to the table view. How to do that? As you can see below, it's not working.
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:config];
NSString *tvdbId = [[NSUserDefaults standardUserDefaults] objectForKey:#"tvdbId"];
NSURL *urlString = [NSURL URLWithString:[NSString stringWithFormat:#"http://api.trakt.tv/show/seasons.json/%#/%#", kApiKey, tvdbId]];
__weak EpisodeViewController *weakSelf = self;
NSURLSessionDataTask *task = [manager dataTaskWithRequest:[NSURLRequest requestWithURL:urlString] completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (!error) {
NSArray *seasons = (NSArray *)responseObject;
__block NSMutableArray *seasonArray = [NSMutableArray new];
[seasons enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *seasonNumber = obj[#"season"];
NSURL *urlString = [NSURL URLWithString:[NSString stringWithFormat:#"http://api.trakt.tv/show/season.json/%#/%#/%#", kApiKey, tvdbId, seasonNumber]];
NSURLSessionDataTask *eposideTask = [manager dataTaskWithRequest:[NSURLRequest requestWithURL:urlString] completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
NSArray *eposides = (NSArray *)responseObject;
NSDictionary *dict = #{#"season": seasonNumber, #"eposodes": eposides};
[seasonArray addObject:dict];
}];
[eposideTask resume];
}];
weakSelf.eposides = [NSArray arrayWithArray:seasonArray];
NSLog(#"%#", weakSelf.eposides);
}
}];
[task resume];
You can Use AFNetworking if you are downloading data
Add your operations (in your case NSURLSessionDataTask) into a NSOperationQueue and
set maximumconcurrentoperationCount to 1
from Completion call back of each operation get the downloaded data (result of operation)
Sample Code
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:#"temp.zip"]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:TempUrl]];
operation = [[AFDownloadRequestOperation alloc] initWithRequest:request targetPath:path shouldResume:YES];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
/// Check Download Progress
}
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//// Success code goes here
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[[self downloadQueue] addOperation:operation];
I am using the AFNetworking library. I can't figure out how to download a file and save it to the documents directory.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"..."]];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"filename"];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation start];
I'm gonna bounce off #mattt's answer and post a version for AFNetworking 2.0 using AFHTTPRequestOperationManager.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"filename"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
AFHTTPRequestOperation *op = [manager GET:#"http://example.com/file/to/download"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"successful download to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
op.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
I'm talking about AFNetworking 2.0
[AFHTTPRequestOperationManager manager] creates manager object with default AFJSONResponseSerializer, and it performs content types restriction. Take a look at this
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
So we need to create a none response serializer and use AFHTTPRequestOperationManager as normal.
Here is the AFNoneResponseSerializer
#interface AFNoneResponseSerializer : AFHTTPResponseSerializer
+ (instancetype)serializer;
#end
#implementation AFNoneResponseSerializer
#pragma mark - Initialization
+ (instancetype)serializer
{
return [[self alloc] init];
}
- (instancetype)init
{
self = [super init];
return self;
}
#pragma mark - AFURLResponseSerializer
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
return data;
}
#end
Usage
self.manager = [AFHTTPRequestOperationManager manager];
self.manager.responseSerializer = [AFNoneResponseSerializer serializer];
[self.manager GET:#"https://sites.google.com/site/iphonesdktutorials/xml/Books.xml"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
if (success) {
success(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (failure) {
failure(error);
}
}];
so that we can get the whole file without any serialization
Documentation page has example with section 'Creating a Download Task':
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil 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) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
NB! Code work with iOS 7+ (tested with AFNetworking 2.5.1)
From AFNetworking docs.
Save to loaded file to your documents.
AFNetworking 3.0
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil 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) {
NSLog(#"File downloaded to: %#", filePath);
}];
[downloadTask resume];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFCompoundResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/octet-stream"];
AFHTTPRequestOperation *operation = [manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (responseObject) {
// your code here
} else {
// your code here
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[operation start];
// manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject: #"application/octet-stream"]; can vary depending on what you expect
Yes, it is better to use AFNetworking 2.0 way with AFHTTPRequestOperationManager. With old way my file did download but for some reason didn't update in file system.
Appending to swilliam's answer, to show download progress, in AFNetworking 2.0 you do similarly - just set download progress block after setting output stream.
__weak SettingsTableViewController *weakSelf = self;
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:newFilePath append:NO];
[operation setDownloadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToRead) {
float progress = totalBytesWritten / (float)totalBytesExpectedToRead;
NSString *progressMessage = [NSString stringWithFormat:#"%# \n %.2f %% \n %# / %#", #"Downloading ...", progress * 100, [weakSelf fileSizeStringWithSize:totalBytesWritten], [weakSelf fileSizeStringWithSize:totalBytesExpectedToRead]];
[SVProgressHUD showProgress:progress status:progressMessage];
}];
This is my method to create bytes string:
- (NSString *)fileSizeStringWithSize:(long long)size
{
NSString *sizeString;
CGFloat f;
if (size < 1024) {
sizeString = [NSString stringWithFormat:#"%d %#", (int)size, #"bytes"];
}
else if ((size >= 1024)&&(size < (1024*1024))) {
f = size / 1024.0f;
sizeString = [NSString stringWithFormat:#"%.0f %#", f, #"Kb"];
}
else if (size >= (1024*1024)) {
f = size / (1024.0f*1024.0f);
sizeString = [NSString stringWithFormat:#"%.0f %#", f, #"Mb"];
}
return sizeString;
}
In addition to the previous answers, with AFNetworking 2.5.0 and iOS7/8 I have found that that the extra step of opening the output stream is also needed to prevent the app from hanging (and eventually crashing from lack of memory).
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:dest
append:NO];
[operation.outputStream open];
[operation start];