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.
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 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).
I have this code to download 40 json
NSMutableArray *mutableOperations = [NSMutableArray array];
for (NSDictionary *dict in general_URL) {
NSURL *url = [dict objectForKey:#"url"];
NSString *key = [dict objectForKey:#"key"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFHTTPResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.all_data setObject:[self parseJSONfile:responseObject] forKey:key];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"progress:%f", (float)numberOfFinishedOperations / totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog (#"all done");
}];
[manager.operationQueue addOperations:operations waitUntilFinished:NO];
As you can see I use a manager to have a queue of request. The problem is that suddenly, it go in timeout with -1001 code.
It happens only in EDGE mode, in wifi and 3g it don't happen.
What's the problem?
If you specify the maxConcurrentOperationCount of the operation queue, that will control how many concurrent operations are attempted, thus mitigating any timeouts resulting from the fact that iOS limits how many simultaneous network connections are permitted:
manager.operationQueue.maxConcurrentOperationCount = 4;
[manager.operationQueue addOperations:operations waitUntilFinished:NO];
In the absence of this, when you submit your 40 operations, all of them are likely to attempt to start NSURLConnection objects, even though only 4 or 5 can really run at at a time. On slow connections, this can result in some of your latter requests timing out.
If you specify the maxConcurrentOperationCount, it won't attempt to start the latter connections until the prior connections have completed. You'll still enjoy the performance benefit of concurrent requests, but you won't be making a bunch of requests that will timeout because of the throttling of concurrent NSURLConnection requests that iOS enforces.
I'm using AFDownloadRequestOperation + AFNetworking to download and resume a list of files from a server. The code is working great at downloading and resuming multiple files at a time. But how to queue all operations inside an operations queue and execute the operation one by one?
Here's my current code
// request the video file from server
NSString *downloadURL = [NSString stringWithFormat:#"%#%#", [recipe download_url], [step valueForKey:#"video"]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:downloadURL]];
AFDownloadRequestOperation *operation = [[AFDownloadRequestOperation alloc] initWithRequest:request targetPath:videoFile shouldResume:YES];
// done saving!
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Done downloading %#", videoFile);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %ld", (long)[error code]);
}];
// set the progress
[operation setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
float progress = ((float)totalBytesReadForFile) / totalBytesExpectedToReadForFile;
[progressBar setProgress:progress];
}];
[operation start];
You'll either need to add the operations to AFHTTPClient's operationQueue, or add them to a NSOperationQueue you've made yourself.
Then set the number of maximum concurrent operations for the queue your using to 1
Change
[operation start];
to
[[YourAFHTTPClientSubclass sharedInstance] enqueueHTTPRequestOperation:operation];
By default, this will have a maximum number of operations "determined dynamically by the NSOperationQueue object based on current system conditions".
If you really want to force everything to one at a time, do:
[YourAFHTTPClientSubclass sharedInstance].operationQueue.maxConcurrentOperationCount = 1;
This will block all network operations until the operation is finished.
Of course, you can make your own operation queue as Audun suggested, but it's probably better to let the system decide what to do based on current conditions.
Depending on your use case, you may want to set the priority of the operations of the video downloads to low:
operation.queuePriority = NSOperationQueuePriorityLow
This will allow other network operations to be placed in the queue at a higher priority than your video downloads.
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.