I'm trying to figure out a way to download multiple images with AFNewtorking 2.0. I've read a lot of posts here in SO, but can't find the answer I'm looking for, hope you guys can help me.
The problem is that I want to know when all of the downloads finished and if all images where downloaded.
So I have an array with image URL's ant trying to do something like this.
for(NSString *photoUrlString in self.photos){
NSURL *url = [NSURL URLWithString:photoUrlString];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image error: %#", error);
}];
[requestOperation start];
}
I've found some answers with putting these requests into a queue and setting max concurrent operations to 1. But don't know how that works really.
Any help is appreciated, thanks in advance!
for(Photo *photo in array){
//form the path where you want to save your downloaded image to
NSString *constPath = [photo imageFullPath];
//url of your photo
NSURL *url = [NSURL URLWithString:photo.serverPath];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
op.responseSerializer = [AFImageResponseSerializer serializer];
op.outputStream = [NSOutputStream outputStreamToFileAtPath:constPath append:NO];
op.queuePriority = NSOperationQueuePriorityLow;
[op setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead){
}];
op.completionBlock = ^{
//do whatever you want with the downloaded photo, it is stored in the path you create in constPath
};
[requestArray addObject:op];
}
NSArray *batches = [AFURLConnectionOperation batchOfRequestOperations:requestArray progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
//after all operations are completed this block is called
if (successBlock)
successBlock();
}];
[[NSOperationQueue mainQueue] addOperations:batches waitUntilFinished:NO];
Try this:
// _group, _queue are iVar variable
dispatch_group_t *_group = dispatch_group_create();
dispatch_queue_t *_queue = dispatch_queue_create("com.company.myqueue2", NULL);
// all files download
for(int i = 0 ; i < numberOfFileDownloads; i++){
dispatch_group_async(_group, _queue, ^{
// here is background thread;
// download file
});
}
// all files are download successfully, this method is called
dispatch_group_notify(_group, _queue, ^{
}
Check out +[AFURLConnectionOperation batchOfRequestOperations:progressBlock:completionBlock:]
Although it's not documented, implementation is self-explanatory. Also it allows you to monitor the progress.
You will need to have an array of HTTP operations prior to using this method (this is if you decided to stick to NSURLConnection-based implementation of AFNetworking).
Related
I have trying to download the multiple .mp3 files from a server at time. One complete audio file is divided into 286 parts. I fetch all the urls of the file and now I want to download 286 files. I search a lot but many library stop downloading when I go back to previous controller and if user minimize the app the downloaded stop. Is there any library which can manage multiple downloads and download didn't stop when user go back to previous controller of minimize the app.
I am using Download Manager library but I can't get my desired. Please give me the solution. I am stuck with that from 3 days . Please tell me the solution . Thanks
In my project I'm using AFNetwirking. You can create a singleton object, and here is my method (for example) for downloading files :
- (AFHTTPRequestOperation *)performDownloadWithURLString:(nonnull NSString *)urlString
destinationPath:(NSString *)destinationPath
progress:(void (^)(long long bytesRead, long long totalBytesToRead))progress
apiCallback:(void(^)(BOOL isSuccessful, id object))apiCallback
{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *fullPath = [[FCFileManager pathForTemporaryDirectory] stringByAppendingPathComponent:[url lastPathComponent]];
[operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:fullPath append:NO]];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
if(progress) {
progress(totalBytesRead, totalBytesExpectedToRead);
}
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if(destinationPath && [FCFileManager isFileItemAtPath:destinationPath]) {
NSError *removeError;
[FCFileManager removeItemAtPath:destinationPath error:&removeError];
}
if(destinationPath) {
[FCFileManager moveItemAtPath:fullPath toPath:destinationPath];
}
dispatch_async(dispatch_get_main_queue(), ^{
if(apiCallback) {
apiCallback(YES, destinationPath ?: fullPath);
}
});
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSError *removeError;
[FCFileManager removeItemAtPath:fullPath error:&removeError];
if(apiCallback) {
apiCallback(NO, [AZError errorWithNSError:error]);
}
}];
[operation start];
}
Hope it helps you.
I'm using AFNetworking to download more or less 200 images. The problem is that the main thread is blocked during the download, not during the success/failure block.
Here is my code:
imageDownloads=[[NSMutableArray alloc]init];
for(NSString *url in liens){
NSString *totalURL = [NSString stringWithFormat:#"http://%#", url];
[imageDownloads addObject:[[ImageDownload alloc] initWithURL:[NSURL URLWithString:totalURL] filename:nil]];
}
for (int i=0; i < imageDownloads.count; i++)
{
ImageDownload *imageDownload = imageDownloads[i];
[self downloadImageFromURL:imageDownload];
}
- (void)downloadImageFromURL:(ImageDownload *)imageDownload
{
NSURLRequest *request = [NSURLRequest requestWithURL:imageDownload.url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
imageDownload.totalBytesRead = totalBytesRead;
imageDownload.totalBytesExpected = totalBytesExpectedToRead;
[self updateProgressView];
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSAssert([responseObject isKindOfClass:[NSData class]], #"expected NSData");
imageDownload.totalBytesExpected = imageDownload.totalBytesRead;
[self updateProgressView];
//all kind of basic stuff here I left out: I get store the data inside CoreData
NSLog(#"finished %#", imageDownload);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", error);
}];
[operation start];
}
Basically, when I launch the code, the thread is blocked for like 30-40 seconds (the pictures are about 100MB in total), and then suddenly I can see all the NSLog logs appear with the "Finished"... text. So that part if really quick. But I thought AFNetworking wasn't supposed to block the main thread while I was downloading? This also doesn't allow me to track the progress of the download...Am I doing something wrong or misinterpreting something?
You're updating the progress view in the progress block. Because AFNetworking is inherently async anyway, each of these requests will stack and run at the same time. If you're running 200 of them, that's going to freeze up the app. Try using NSOperationQueue's maxConcurrentOperationCount to limit the number of concurrent threads.
Alternatively, you could save all the trouble and just use sdwebimage.
I am using AFNetworking Library,
How to download the large amount of data using AFNetworking Library to show the better performance?
I have followed the following steps:
".zip" files downloading the content from server.
I have added one url , like this i have so many url's to download the content from the server. It takes too much time. How to perform the fast downloading from server.
My Query is: If the url was not found on server, then it executes failure state.
but the ".zip" is storing in documents folder with empty content like zerobytes.
while am trying to extract the .zip it shows .cpgz format.
Let us know how to resolve this?
-(void)downloadSingFile:(NSUInteger )activityAtIndex totalCount:(NSUInteger )lessonTotalCount{
// http://118.102.131.158/IYG_wrapper/IYG_updated/IYG_G7_L03/IYG_UN_Mall.zip
NSString *activityUrl = [filterObjects objectAtIndex:activityAtIndex];
NSURL *zipFileAtUrl = [NSURL URLWithString:[activityUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
BOOL success = [libraryObj createFolderWithName:rootName];
if (success) {
activityPath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:rootName] stringByAppendingPathComponent:[zipFileAtUrl lastPathComponent]];
}
NSURLRequest *request = [NSURLRequest requestWithURL:zipFileAtUrl];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:activityPath append:NO];
[operation setCompletionBlock:^{
sleep(1);
NSLog(#"sucessfully downloaded file %d",activityIndx);
[self extractSingleActivityAtindex:activityIndx];
}];
[operation start];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float percentDone = ((float)(totalBytesRead) / (float)(totalBytesExpectedToRead));
self.tableView.alpha = 0.7;
progressView.hidden = NO;
progressAlert.hidden = NO;
progressAlert.labelText = #"Downloading..";
[self updateProgress:percentDone];
NSLog(#"progress float value is: %.2f",percentDone);
}];
}
1.Please let us know, now i dont want to download the which is exist in documents folder path and also let us know whether the the file has been downloaded sucessfully or not.
A couple of thoughts:
If you want to know if it succeeded or not, rather than using setCompletionBlock, you should use setCompletionBlockWithSuccess:failure:.
In a related topic, I might be inclined to download the file to some temporary file (in the NSTemporaryDirectory() folder) and only once it was successfully downloaded, move it to the Documents folder. You want to completely eliminate the possibility of any in-progress download resulting in a file in Documents.
If you don't want to download a file that already exists in your Documents folder, then you have to program that logic yourself (i.e. use NSFileManager to see if the file exists, and if not, only then initiate the download).
Eliminate the sleep call. You never want to sleep (esp on the main thread). If you really want to trigger something to happen one second later, use dispatch_after:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"sucessfully downloaded file %d",activityIndx);
[self extractSingleActivityAtindex:activityIndx];
});
You need to take care of below points to achieve your functionality
Check file is already exist or not before download
Implement failure block and remove file at your activityPath
For reference, please check below code snippet.
-(void)downloadSingFile:(NSUInteger )activityAtIndex totalCount:(NSUInteger )lessonTotalCount{
// http://118.102.131.158/IYG_wrapper/IYG_updated/IYG_G7_L03/IYG_UN_Mall.zip
NSString *activityUrl = [filterObjects objectAtIndex:activityAtIndex];
NSURL *zipFileAtUrl = [NSURL URLWithString:[activityUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
BOOL success = [libraryObj createFolderWithName:rootName];
if (success) {
activityPath = [[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:rootName] stringByAppendingPathComponent:[zipFileAtUrl lastPathComponent]];
}
if ([[NSFileManager defaultManager]fileExistsAtPath:activityPath]) {
NSLog(#"File Already Exist");
[self extractSingleActivityAtindex:activityIndx];
return;
}
NSURLRequest *request = [NSURLRequest requestWithURL:zipFileAtUrl];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:activityPath append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
sleep(1);
NSLog(#"sucessfully downloaded file %d",activityIndx);
dispatch_async(dispatch_get_main_queue(), ^() {
[self extractSingleActivityAtindex:activityIndx];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// NSLog(#"ERR: %#", [error description]);
[[NSFileManager defaultManager]removeItemAtPath:activityPath error:nil];
}];
[operation start];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float percentDone = ((float)(totalBytesRead) / (float)(totalBytesExpectedToRead));
self.tableView.alpha = 0.7;
progressView.hidden = NO;
progressAlert.hidden = NO;
progressAlert.labelText = #"Downloading..";
[self updateProgress:percentDone];
NSLog(#"progress float value is: %.2f",percentDone);
}];
}
I download asynchronously some object, I store it in array. Next for each object I download some coordinates with geocoding (it is also asynchronously), and update my database for each object with new parameters which is coordinate. My method looks like this:
- (void)downloadObjectsWithTitle:(NSString *)title andHandler:(void(^)(NSMutableDictionary *result))handler {
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET"
path:nil
parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//I get here array of objects
//now for each object I want to download geocoding localization so i called another asynchronyous method getLocationWithTitle:andHandler;
for(int i = 0; i < resutArray.count; i++) {
[self downloadLocationWithString:[dictionary objectForKey:#"string"] andHandler:^(NSMutableDictionary *result) {
//update database;
}];
}
handler(dictionary);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation start];
}
My question is how to downalod coordinates for each object and that fire:
handler(dictionary);
so wait for each coordinates download (for each object) before quit method (fire handler).
Thnaks for all sugestions.
Maintain a count of all the tasks. When it's zero you're done.
Assuming you're using dispatch_async in downloadLocationWithString: on a concurrent queue:
dispatch_barrier_async(queue, ^{
// will only be called after all the blocks submitted to queue have finished.
}];
(If you're using serial queue, simply call handler at the last line of the last block)
Try a global flag. set NO first. In download block, after download complete set flag to yes. You can check that flag.
In my iOS App,I have hundreds of image download.
I use AFNetworking to get those images.I want to manage the request number of AFWorking.
This is my code:
The problem is:It will block my UI.
THX for help me!
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
for (NSString *urlString in self.downloadImageList) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSString *filename = url.lastPathComponent;
NSURL *outputFileURL = APPLICATION_DOCUMENTS_DIRECTORY;
outputFileURL = [outputFileURL URLByAppendingPathComponent:[NSString stringWithFormat:#"%#/images/%#",self.boardId,filename]];
dispatch_group_async(group, dispatch_get_main_queue(), ^{
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamWithURL:outputFileURL append:YES];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.downloadImageList removeObject:urlString];
NIDINFO(#"download success %#",filename);
dispatch_semaphore_signal(semaphore);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NIDERROR(#"download image error:%#\n%#",error,urlString);
dispatch_semaphore_signal(semaphore);
}];
[operation start];
});
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
dispatch_release(group);
The problem is: It will block my UI.
Well yeah, that's sort of the point of dispatch_semaphore_t. If you remove those calls, your AFNetworking calls will execute asynchronously in the background without affecting your main / UI thread.
I don't know the full context of where this code is being used, but it looks like you may want to consider having the method that calls this take a block parameter, that can be called once all of the requests have finished. You may also want to take a look at AFHTTPClient's batch operations features, which allows you to track the progress of a group of operations, getting callbacks for each individually, and when all of then finish.