i'm developing an video download application but there is a problem. User did enter background (while downloading), after several minutes ago, when user did call back application from background, the downloading file get a problem. For example, the file must resume from (for example) 34%. it resumes but downloading completing on 134% !? in other words, i get %100 of the file but, on %134. is anybody have an idea? Sorry for my bad english.
Below, there are my codes i used, got from AFNetworking ;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
To see percentage of the file, i use following codes;
[[MUtility sharedObject].operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
NSLog(#"Download = %f", (float)totalBytesRead / totalBytesExpectedToRead *100);
}];
Related
In my app, I am needing to download about 6,000 images. I realize this is a lot, but it is needed.
Currently I am using doing the following:
NSArray *photos = #[hugeAmountOfPhotoObjects];
for (ZSSPhoto *photo in photos) {
[self downloadImageWithURL:photo.mobileURL progress:^(double progress) {
} completion:^(UIImage *image) {
// Save the image
} failure:^(NSError *error) {
}];
}
...
- (void)downloadImageWithURL:(NSURL *)url progress:(void (^)(double progress))progress completion:(void (^)(UIImage *image))completion failure:(void (^)(NSError *error))failure {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setTimeoutInterval:600];
self.operationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
completion(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self processOperation:operation error:error failure:failure];
}];
[requestOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
double percentDone = (double)totalBytesRead / (double)totalBytesExpectedToRead;
progress(percentDone);
}];
[self.operationQueue addOperation:requestOperation];
}
The problem here is that it takes forever to download using this method, and some of my users are reporting crashing because of high memory usage.
Is there a better method that I could be using to download such a large number of image files?
You could try this somewhat recursively
NSMutableArray *undownloaded;
- (void) startDownload {
undownloaded = [photos mutableCopy]; //get a list of the undownloaded images
for(int i = 0; i < 3;i++) //download 3 at a time
[self downloadImage];
}
- (void) downloadImage {
if(undownloaded.count > 0){
ZSSPhoto *photo = undownloaded.firstObject;
[undownloaded removeObjectAtIndex:0];
[self downloadImageWithURL:photo.mobileURL progress:^(double progress) {
} completion:^(UIImage *image) {
// Save the image
[self downloadImage];
} failure:^(NSError *error) {
[self downloadImage];
//[undownloaded addObject:photo]; //insert photo back into the array maybe to retry? warning, could cause infinite loop without some extra logic, maybe the object can keep a fail count itself
}];
}
}
warning: untested code, may need some tweaking
The speed problem can be solved (the speed will increase, but it might still be slow) with multithreading, downloading all the images at the same time instead of one per time. However, the memory problem is a bit more complicated.
ARC will release all the images after everything is finished, but right before that you gonna have 6,000 images in the device memory. You could optimize the images, reduce their resolution or download them in steps, like Google Images do (you download the images that will be visible at first, then when the user scrolls down you load the images in the new visible area; downloading the images only when they are needed).
Considering that you are downloading enough images to give a memory problem, you will probably take a lots of space in your user's device if you download all of them, and the 'steps' solution may solve that as well.
Now, let's suppose you must download all of them at the same time and space isn't a problem: I suppose that if you put the downloadImageWithURL:progress: method inside a concurrent queue, the images are gonna be freed from memory just after saving (it's just a supposition). Add this to your code:
dispatch_queue_t defaultPriorityQueueWithName(const char* name)
{
dispatch_queue_t dispatchQueue = dispatch_queue_create(name, DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t priorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_set_target_queue(dispatchQueue, priorityQueue);
return dispatchQueue;
}
And change your code to that:
dispatch_queue_t threadItemLoadImage = defaultPriorityQueueWithName("DownloadingImages");
NSArray *photos = #[hugeAmountOfPhotoObjects];
for (ZSSPhoto *photo in photos)
{
dispatch_async(threadItemLoadImage, ^
{
[self downloadImageWithURL:photo.mobileURL progress:^(double progress) {
} completion:^(UIImage *image) {
// Save the image
} failure:^(NSError *error) {
}];
});
}
You will need to remove setDownloadProgressBlock: in case it updates some view, since they will be downloaded simultaneously. Also, a warning: totalBytesExpectedToRead not always will be correctly retrieved at first, containing 0, which might make your app crash for dividing by zero as well in some rare occasions. In future cases, when you need to use setDownloadProgressBlock:, check totalBytesExpectedToRead value before doing that division.
I am completely baffled on this. Each time I test my app in the simulator or on a real device, it hangs for 30,40,60 seconds on this bit of code, but all following request to this API call will load in milliseconds.
I thought it was related to DNS resolving for the first time, so I switched to an IP address for testing and that did not resolve the issue.
If it's the first request after the app starts, it will just hang for a large amount of time, once it has loaded, you can open the view for the same data set or another and it load the list very fast.
Any recommendations?
-(void)getVendorImages {
//Alloc the image list
self.imageList = [[NSMutableArray alloc] init];
// Prepare the request
NSString* vendorImagesApi = [NSString stringWithFormat:#"%#%#",#"http://example.com/api/v1/vendor/images/",self.imageData.vendorId];
NSLog(#"Getting list of images %#",vendorImagesApi);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:vendorImagesApi
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// NSLog(#"JSON: %#", responseObject);
//Get images
for (id imageData in responseObject)
{
// prepare image url
NSString* imageUrl = [NSString stringWithFormat:#"%#%#%#",#"http://example.com/images/",imageData[#"id"],#"-650x650.jpg"];
NSLog(#"Putting this in in a list: %#", imageUrl);
[self.imageList addObject:imageUrl];
}
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
EDIT:
Here is the thread stack
So after a bunch of digging, I removed all the files for AFNetworking, then installed it all again, including the UIKit+AFNetworking folder, after that I removed all the frameworks and added back UIKit and SystemConfig. Lastly one of my views that loaded at the start of the app had it's own NSURLConnectionDelegate. I removed all that and had it use AFNetworking, and that did the trick. Apparently the first run that was stalling the connection for AFNetworking was because it was likely fighting over who could use the service.
I am downloading movie files from UIGridViewCells. My code is:
NSMutableURLRequest* rq = [[APIClient sharedClient] requestWithMethod:#"GET" path:[[self item] downloadUrl] parameters:nil];
[rq setTimeoutInterval:5000];
_downloadOperation = [[AFHTTPRequestOperation alloc] initWithRequest:rq] ;
_downloadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:[[self item] localUrl] append:NO];
__weak typeof(self) weakSelf = self;
[_downloadOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", [weakSelf.item localUrl]);
[Helper saveItemDownloaded:weakSelf.item.productId];
weakSelf.isDownloading = NO;
[weakSelf.progressOverlayView removeFromSuperview];
[weakSelf setUserInteractionEnabled:YES];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
[weakSelf.progressOverlayView removeFromSuperview];
[weakSelf setUserInteractionEnabled:YES];
weakSelf.isDownloading = NO;
}];
[_downloadOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float progress = totalBytesRead / (float)totalBytesExpectedToRead;
weakSelf.progressOverlayView.progress = progress;
}];
[[NSOperationQueue mainQueue] addOperation:_downloadOperation];
And the property in ItemCell is:
#property (nonatomic, retain) AFHTTPRequestOperation *downloadOperation;
After 1-2 successful downloads(20mb), I am receiving Memory Warning. Memory using is increasing with each download, and never decrease when the download finishes.
From Instruments:
I believe the preferred method of downloading files with AFNetworking is by setting the "outputStream" property.
According to AFNetworking documentation:
The output stream that is used to write data received until the request is finished.
By default, data is accumulated into a buffer that is stored into responseData upon completion of the request. When outputStream is set, the data will not be accumulated into an internal buffer, and as a result, the responseData property of the completed request will be nil. The output stream will be scheduled in the network thread runloop upon being set.
I was having the same problem, solved it by using "outputStream".
Use #autorelease per file downloaded:
for(File* file in fileList)
{
#autoreleasepool {
[self downloadFile:file];
}
}
This will release all variables and data allocated between separate files you download.
Also you should track down those memory leaks. I see some visible in instruments screenshots.
I am using AFDownloadRequestOperation. It was working fine, but is now suddenly giving this error every time I run it (I am downloading from the same URL every time I run it and I do clear the Documents and Temp folders in AppDelegate).
Initiate a download. (Before downloading, I do manually check that there is nothing in Temp or Documents folder, i.e. where I store the files once download is done).
The download gets over in 1-2 seconds, code moves to success block, the debug console shows a 206 status code, a file magically gets placed in Documents folder out of nowhere and has the exact file size (it used to get downloaded properly until I started running into this issue). The file is corrupt and does not open.
Here is the code snippet -
download_operation_progress_block = ^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
float progress = ((float)totalBytesReadForFile) / totalBytesExpectedToReadForFile;
dataFetcherObject.downloadProgressDetails.downloadPercentage = [NSNumber numberWithFloat:floorf(progress*100)];
};
download_operation_success_block = ^(AFHTTPRequestOperation *operation, id responseObject) {
dataFetcherObject.downloadProgressDetails.isDownloadDone = [NSNumber numberWithBool:YES];
dataFetcherObject.isDataFetched = #"Yes";
};
download_operation_failure_block = ^(AFHTTPRequestOperation *operation, NSError *error) {
[dataFetcherObject.downloadProgressDetails.downloadOperation cancel];
dataFetcherObject.isDataFetched = #"Error";
};
AFDownloadRequestOperation *operation = [[AFDownloadRequestOperation alloc] initWithRequest:contentDownloadRequest
targetPath:path
shouldResume:YES];
[operation setCompletionBlockWithSuccess:download_operation_success_block
failure:download_operation_failure_block];
[operation setProgressiveDownloadProgressBlock:download_operation_progress_block];
// operation.deleteTempFileOnCancel = TRUE;
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];
dataFetcherObject.downloadProgressDetails.downloadOperation = operation;
What could be the reason for this.
I accidentally mistyped the post path and noticed that although it's being wrong, the success block is called:
[[APIClient sharedInstance]
postPath:#"api_url"
parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Result: Success %#",[responseObject description]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//handle error
NSLog(#"Result: Failure + %#",error.userInfo);
}];
Of course the data are not being sent to server and the transaction is not processed, but I want to know why it's not the failure block which is supposed to be called in case the path is wrong? Thanx.
Failure is called if the requestOperation has an associated error after finishing. Reasons for an error include the response having the incorrect Content-Type, not having an acceptable status code (2XX range, by default), or an error processing the downloaded data.
Why your server returned a 200 response with the correct content type is a question only something you can determine.