How to improve the speed while using NSURLSessionDownloadTask? - ios

I need to download some images and a video from server:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
for (int i = 0; i < _collectionArr.count; ++i) {
NSURL *URL = [NSURL URLWithString:URL_ADDRESS(portNumber,_collectionArr[i])];
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);
if (i == _collectionArr.count - 1) {
[SVProgressHUD showSuccessWithStatus:#"Done"];
}
}];
[downloadTask resume];
}
I found that it is so slow! The speed is about 200K/s and the time with Android is 2/3 less than iPhone.
When I download a mp4 about 4.6M, it takes 15s.
2014-11-29 13:46:35.071 testDownload[2105:47825] Begin
2014-11-29 13:46:51.740 testDownload[2105:47825] File downloaded to: file:///Users/apple/Library/Developer/CoreSimulator/Devices/259F2CB8-01FD-47C2-A38F-6100A2FF350A/data/Containers/Data/Application/71E553BC-F85D-4BFA-8937-FE9026FDF65C/Documents/VTS_01_audi.mp4
But when I using other app to download movies it can be 2M/s. Am I wrong when I use afnetworking ? How it happens and what can I do to deal with it.
Another question is that I know it's wrong to monitor the last request with if (i == _collectionArr.count - 1){[SVProgressHUD showSuccessWithStatus:#"Done"];} But I don't know the right answer.
Thanks for your help.

A couple of thoughts:
The removal of the HUD when i hits count - 1 is not correct. You actually may remove it before they're all done. If you start two downloads, one huge one and one tiny one, the HUD will be dismissed when the second one finishes, but the other one might not be done yet. These run concurrently, so you have to wait until they're all done, not just when the last submitted one is done.
One way to do this is to use a dispatch group (which you enter as you submit the requests, leave in the completion block, and then add a dispatch group notification that removes the HUD).
You're not doing anything else outside of this code that might be blocking the main thread are you? AFNetworking dispatches its completion blocks to the main queue, and if you block the main thread for anything, it will affect this performance. You could either use Instruments to identify waiting threads, or, for testing purposes only, temporarily change the completionQueue of the manager to be some custom queue of your own creation.
For example, before your for loop, do something like:
manager.completionQueue = dispatch_queue_create("com.example.app.netCompletionQueue", NULL);
That would take main thread blocking out of the equation (though this strikes me as unlikely unless you're doing something extraordinary in the main thread). Once you confirm this is not the issue, though, comment out that line, because you really do want to use the main thread for your completion blocks.
Once you confirm the perf problem is not result on main thread contention issue, I'd suggest doing some benchmarking of alternative techniques (e.g. your own NSURLSession, etc.). This could help diagnose whether the problem is AFNetworking, rather than something like simulator vs device performance, caching, etc. Frankly, I find it unlikely that AFNetworking is, itself, the problem, though.

Related

Need to download 50,000 images or more

Currently, i am downloading multiple images with below code. In My app need to download 50,000 images or more.
When memory consumption reaches to 600 MB application crash due to memory pressure.
When I download 10K images its working fine.But when I want to download 20K images from a server then after downloading 9-10K images application crash due to memory pressure.
I also try to Instrument to find memory leak but there is no memory leak.
In Debug Session (When download 20K images) :
Memory Consumption : 500-600 MB and After that its crash.
CPU Usage : 130 -160%
Can you please help me what I did wrong in my code?
- (void)Downloadimages:(NSMutableArray *)aMutArray
{
//newchange
SDImageCache *imageCache = [SDImageCache sharedImageCache];
[imageCache clearMemory];
[imageCache clearDisk];
// NSLog(#"DocumentsDirectory Path : %#", DocumentsDirectory);
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] init];
static AFURLSessionManager *sessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:sessionConfiguration];
});
__block int iCounter = 0;
for (NSInteger aIndex = 0; aIndex < aMutArray.count; aIndex++)
{
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:aMutArray[aIndex]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:(aMutArray.count * 120)];
// urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:aMutArray[aIndex]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:100];
//NSLog(#"%#",urlRequest);
// [urlRequest setTimeoutInterval:(aMutArray.count * 120)];
NSURLSessionDownloadTask *downloadTask = [sessionManager downloadTaskWithRequest:req progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
return [NSURL fileURLWithPath:[DocumentsDirectory stringByAppendingPathComponent:urlRequest.URL.lastPathComponent]];
}
completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
iCounter ++;
if (!error){
// NSLog(#"(%ld) SUCCESS : %#",(long)aIndex, aMutArray[aIndex]);
}
else
{
NSLog(#"(%ld) ERROR : %#",(long)aIndex, aMutArray[aIndex]);
// [CommonMethod DeleteImageWithName:filePath.lastPathComponent];
}
[labelSyncProducts setText:[NSString stringWithFormat:#"Syncing Images %d of %lu", iCounter, (unsigned long)aMutArray.count]];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
if (aMutArray.count == iCounter)
{
[self controlsEnableDisable:true];
}
}];
[downloadTask resume];
}
}
I will suggest you not to download this many images in the app, this will increase your app size on device.
Even though you want to do, then this is the solution.
As you know, every image you download will store temporary in RAM. As number of images increases RAM occupation increases. At one point RAM will full and app terminates.
For solving this, Download some images and save them. After saving, remove this images from RAM and start downloading some more images and save them.
Continue this process until you are done with all images.
This may solve your problem.
The problem is not in the amount of images to download, Download task does not use memory to hold images, but rather just save the image directly to sandbox which is fine. But the problem here lies in the concurrent NSURLSession that you run. Imagine you have 50,000 NSURLSession tasks in memory. That is what make the app crash.
for (NSInteger aIndex = 0; aIndex < 50000; aIndex++)
{
// NSURLSESSION BLOCK
}
What you need to do is a queued downloads. You need to handle this by yourself. For example, at one time, download 10 images first, then after 10 is done, then download next 10 and so on. But... it is crazy really. Who would want to wait for 50,000 images to be downloaded???

Issue with NSURLSession with multiple downloads in background with multiple sessions and multiple segments

We have crated session with below configuration code. I call this method for each task I crate.
+(NSURLSession ) getNewSessionWithID:(NSString )sessionID delegateObject:(id)sender
{
NSURLSession *session;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount=10;
queue.name=sessionID;
session = [NSURLSession sessionWithConfiguration:configuration delegate:sender delegateQueue:queue];
NSLog(#"Session ID :%#",session);
NSLog(#"QUEUE : %#",queue);
return session;
}
Even multiple sessions are created for multiple tasks only one session is active and only one task is executing and for that task only three part are downloading for that one session.
This method is called to create and start download task.
-(void)DLRequestAllRenge:(NSMutableArray*)arrayrange andFileinfo:(FileInfo *)fileInfoObj
{
NSMutableArray *arrayAllParts=[[NSMutableArray alloc]init];
fileInfoObj.tempPath=[fileInfoObj UniqueFileName:[NSTemporaryDirectory() stringByAppendingString:[fileInfoObj.Name stringByDeletingPathExtension]]];
[self createTempDirectory:fileInfoObj.tempPath];
NSLog(#"REQ Session:%#",fileInfoObj.session);
for (int i=0; i<arrayrange.count; i++)
{
NSString *rangString = [arrayrange objectAtIndex:i];
NSMutableURLRequest *request=[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:fileInfoObj.URL]];
[request setValue:rangString forHTTPHeaderField:#"Range"];
NSString *fileName=[NSString stringWithFormat:#"%#(%d).data",[fileInfoObj.Name stringByDeletingPathExtension],i];
NSString *filePath=[fileInfoObj.tempPath stringByAppendingPathComponent:fileName];
FileInfo *subFileInfo=[[FileInfo alloc]init];
subFileInfo.URL=fileInfoObj.URL;
subFileInfo.Name=fileName;
subFileInfo.Path=filePath;
subFileInfo.Folder=[fileInfoObj getCurrentFolderName:filePath];
subFileInfo.Range=rangString;
subFileInfo.isDownloaded=NO;
subFileInfo.NSUrlSessionID=fileInfoObj.NSUrlSessionID;
subFileInfo.Progress=#"0";
subFileInfo.Priority=[NSString stringWithFormat:#"%d",i];
subFileInfo.fileDetail=#"Connecting...";
subFileInfo.fileStatus=RequestStatusDownloading;
subFileInfo.request=request;
subFileInfo.startTime=[NSDate date];
NSURLSessionDownloadTask *downloadTask = [fileInfoObj.session downloadTaskWithRequest:request];
downloadTask.taskDescription=filePath;
[downloadTask resume];
subFileInfo.DownloadTask=downloadTask;
[arrayAllParts addObject:subFileInfo];
}
fileInfoObj.parts=arrayAllParts;
[downloadingArray addObject:fileInfoObj];
[bgDownloadTableView reloadData];
}
Issue 1
Why all sessions are not active?
Issue 2
Why only three part for one task and one session is downloading?
Is there any way we can activate more session for download more part concurrently?
Please help me with this. Any help is appreciated.
Update
We are able to download data using the above code but the issue is I am not able to get any downloaded data when any downloading task is stopped using -suspend or -cancel.
Is there any way I am able to retrieve raw data not the resumeData but the original downloaded data?

Clean GCD queue for all the rest requests in queue

I have a cache of files in which I need to write/read images.
All the work with the file system I need to perform in background.
For this purposes for saving files I use:
dispatch_barrier_async([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
[data writeToFile:tileFilePathName atomically:YES];
});
And for reading:
__block UIImage *tileImage = nil;
dispatch_sync([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
tileImage = [[UIImage imageWithContentsOfFile:tileFilePathName] retain];
dispatch_async(dispatch_get_main_queue(), ^{
completion (tileCoordValue, side, tileImage, error);
[tileImage release];
});
});
Everything works well, there is a great amount of files in cash folder, but I sometimes I need to clean the cash. I need to cancel all the queue blocks and perform a block with cleaning the folder.
The first my realization of the method looks like this:
+ (void) cleanCash
{
NSString *folderPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"cash"];
dispatch_sync([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
NSParameterAssert(error == nil);
}
});
}
But I have a numerous problems with it because of it is not cancel the all the waiting operations in queue. I try to look solutions in SO but can't implement them unfortunately. Can anyone help me with this?
You can use NSOperationQueue with maxConcurrentOperationCount = 1 (it will make a serial queue) for run NSOperation that will load your images;
NSOperation is a cancelable object. Also you can make dependencies between operations.
P.S.
You can view SDWebImage, it contains SDImageCache class. It class is very good for manage cache of images.

Implementing an async task in iOS

I have a View controller where I have a button which brings up the front camera. The user then takes a photo, the camera controller is dismissed and I show the picture taken in an Image View in the View Controller. Now, I start uploading the server to Amazon S3.
It does take some time for the uploading to complete and the user has to wait for that time before moving to the next view.
I would ideally like to move the user to the next view and let the uploading complete in the background. Is there some way I could do this uploading task in the background ? I know their is something called dispatch_queue which could be used to do this but I am not sure how. If I put the uploading code inside a queue in the view controller file and then move to the next view controller, will not the reference of the previous one be lost.
EDIT
I tried doing the following -
Making a new class which would be responsible for uploading to Amazon S3.
This new class is a delegate to AmazonServiceRequest which should be called when the uploading to Amazon S3 is complete.
On completion uploading to S3, I make a call to my server to save the URL in the database.
I make an object of this class inside the queue block like follows -
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
RIDEUploadPhotoService *uploadPhotoServiceObj = [[RIDEUploadPhotoService alloc] init;
[uploadPhotoServiceObj uploadAndSaveImage:imageToSave];
});
It seems to me that the control never comes inside the request complete which should get called when uploading to Amazon S3 is complete.
Just use one of the asynchronous upload-mechanisms of NSURLConnection/NSURLSession, so you don't have to mess around with background threads. For example:
NSURL *fileURL = [NSURL fileURLWithPath:...];
NSURLRequest *request = [NSURLRequest requestWithURL:...];
// modern way: NSURLSession
NSURLSessionUploadTask *uploadTask = [[NSURLSession sharedSession] uploadTaskWithRequest:request fromFile:fileURL completionHandler:...];
[uploadTask resume];
// old way: NSURLConnection
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:...];
// another old way: synchronous NSURLRequest executed in a background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
});
No. The reference will not be lost.
You can use - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg to perform the method aSelector in the background. If you have any delegates after the completion of the task, they will still be called.

What is the best way to load a remote image?

I've been researching and haven't found any answer to this question - sendAsynchronousRequest vs. dataWithContentsOfURL.
Which is more efficient? more elegant? safer? etc.
- (void)loadImageForURLString:(NSString *)imageUrl
{
self.image = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError)
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data) {
self.image = [UIImage imageWithData:data];
}
}];
}
OR
- (void)loadRemoteImage
{
self.image = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData * imageData = [NSData dataWithContentsOfURL:self.URL];
if (imageData)
self.image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.image) {
[self setupImageView];
}
});
});
}
So I've come up with an answer for my own question:
Currently there are 3 main ways to load images async.
NSURLConnection
GCD
NSOperationQueue
Choosing the best way is different for every problem.
For example, in a UITableViewController, I would use the 3rd option (NSOperationQueue) to load an image for every cell and make sure the cell is still visible before assigning the picture. If the cell is not visible anymore that operation should be cancelled, if the VC is popped out of stack then the whole queue should be cancelled.
When using NSURLConnection + GCD we have no option to cancel, therefore this should be used when there is no need for that (for example, loading a constant background image).
Another good advice is to store that image in a cache, even it's no longer displayed, and look it up in cache before launching another loading process.
sendAsynchronousRequest is better, elegant and whatever you call it. But, personally, I prefer creating separate NSURLConnection and listen to its delegate and dataDelegate methods. This way, I can: 1. Set my request timeout. 2. Set image to be cached using NSURLRequest's cache mechanism (it's not reliable, though). 2. Watch download progress. 3. Receive NSURLResponse before actual download begins (for http codes > 400). etc... And, also, it depends on cases like, image size, and some other requirements of your app. Good luck!

Resources