Updating UI (labels) while downloading Images in iOS - ios

This is the function to download images , in this function i have to update the UI, the UILabels that shows total images and counter value (downloaded images).
thats why i am calling a thread.
-(void) DownloadImages
{
NSLog(#"Images count to download %lu",(unsigned long)[productID count]);
if ([productID count] > 0)
{
// Document Directory
NSString *myDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for (i = 0; i < [productID count]; i++)
{
[NSThread detachNewThreadSelector: #selector(UpdateLabels) toTarget:self withObject:nil];
// [self UpdateLabels]; this does not work….
NSLog(#"image no %d", i);
//[self Status];
NSString *imageURL = [NSString stringWithFormat:#"http://serverPath/%#/OnlineSale/images/products/%#img.jpg",clientSite,[productID objectAtIndex:i]];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
// Reference path
NSString *imagePath = [NSString stringWithFormat:#"%#/%#-1-lrg.jpg",myDirectory,[productID objectAtIndex:i]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];
[imageData writeToFile:imagePath atomically:YES];
}
NSLog(#"Downloading images completed...");
}
}
Problem:
- here for every new image i have to call every time a new NSThread, there may be thousands of images and it looks bad that there will be thousands threads.
in simulator i have tested it for 3000 images, but in my iPad when testing it gives the error,,,
App is Exited and terminated due to memory pressure.
i guess the error is due to this calling approach of new thread every time.
what i am doing wrong here.?????????????
i have searched there are two different options but i don't know which one is right option,
Options are:
- [self performSelectorInBackground:#selector(UpdateLabels) withObject:nil];
- [NSThread detachNewThreadSelector: #selector(UpdateLabels) toTarget:self withObject:nil];
and i also want to know that if i use
[NSThread cancelPreviousPerformRequestsWithTarget:self];
just after the call of selector, is it right approach or not..???

My suggestion is to use NSOperationQueue . you could then set the maxConcurrentOperationCount property to whatever you like.
In addition in your case is the best approach.
Make some research on what are your maxConcurrentOperationCount with how many concurrent thread your iphone could run without any issue.
NSOperationQueue will manage this for you run new thread with this limitation.
It should look something like this:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue setMaxConcurrentOperationCount:500];
for (i = 0; i < [productID count]; i++) {
[myQueue addOperationWithBlock:^{
// you image download here
}];
}
but check apple reference first.

Related

Multiple webservice Making UI unresponsive

The problem is that I am calling multiple webservices on my homepage and the webservice is returning me the images and text from the server. During this process the UI become fully unresponsive for say 1-2 minutes which is looking very bad as I cant do anything. I heard about dispatch and tried to implement it but I dont get any results.May be I am doing something wrong.
What I want now that I want to that I want to run this process in background So that a user can interact with the UI during the fetching operation from the server. I am implementing my code just tell me where to use dispatch.
-(void)WebserviceHomeSlider{
if([AppDelegate appDelegate].isCheckConnection){
//Internet connection not available. Please try again.
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Internate error" message:#"Internet connection not available. Please try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
return;
}else {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
[manager GET:ServiceUrl#"fpMainBanner" parameters:nil success:^(AFHTTPRequestOperation *operation,id responseObject)
{
//NSLog(#"JSON: %#", responseObject);
arrSlider = [responseObject objectWithJSONSafeObjects];
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
}
Just tell me where to use dispatch or edit my code using dispatch if possible. I have gone through some examples but still my concept is not clear. Which dispatch method id best (DEFAULT or BACKGROUND). I will be very thankful to You.
This is the code you are looking for . Just tell me where to edit it using dispatch
-(NSArray *)getText{
NSMutableArray *textArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
texxt=[[arrSlider objectAtIndex:i ]valueForKey:#"title" ];
[textArr addObject:[texxt uppercaseString]];
}
return textArr;
}
-(NSArray *)getsubText{
NSMutableArray *subtext = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
subbtext=[[arrSlider objectAtIndex:i ]valueForKey:#"tagline_value" ];
if(i==8)
{
subbtext=#"MAKE YOURSELF STAND OUT GET YOUR FREE CARDS!";
}
NSLog(#"subtext is,,.,.,,.,%#.%#",#"k",subbtext);
[subtext addObject:[subbtext uppercaseString]];
}
return subtext;
}
-(NSArray *)getImages
{
NSMutableArray *mArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
}
return mArr;
}
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
The problem here is that you didn't showed implementation of addImagesFromResources method. You probably have to implement GCD in this method because i guess you are fetching images and setting on UI by this your main thread is getting blocked.
You are trying to access the UIView from thread other than the main which causes the UI unresponsiveness.
Please use this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
});
What I think is that your UI hangs due to downloading of image.
So you should use SDWebImage library that can help you to cache image and also prevent the UI getting hang. Using SDWebImage you can show the loader for the time the image is not loaded and it will cache the image. So from next time no image will be downloaded and also the UI will not hang. The link for complete reference to SDWebImage is :https://github.com/rs/SDWebImage
This block blocking your main UI to perform tasks so update this block as follow,
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
dispatch_async(dispatch_get_main_queue(), ^{
// If you want to refresh your UI simultaniously, you can write statement for that i.e
// yourImageView.image = img;
// Otherwise remove this queue
});
});
}
The main thread should never be used for long processing. Its there for the UI.
AFNetworking provides asynchronous functionality for downloading files. You should also be careful of thrashing the network layer with too many simulatanous downloads. I think 4 or 5 is the max the last time I tested.
So the flow of execution should be as follows.
The controller sets up the slideshow, sends the downloading of the images to a background thread(automatically handled by AFNetworking), passing a completion block to it. In this completion block you need to dispatch the work back to the main thread using GCD, dispatch_async to dispatch_get_get_main_queue
Keep your main thread's run loop available for user interaction.
It is because the images are loading in another thread and you are populating your slider right after getting the data.
It is better to implement this protocol method of KASlidershow to use the slideshow in a more memory efficient way.
(UIImage *)slideShow:(KASlideShow *)slideShow imageForPosition:(KASlideShowPosition)position
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
return img;
});
}
i didn't try the code but i hope it will work with little changes in variable names. Good luck

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.

Memory management in iOS , Simulator vs idevice gives different result

i am downloading images from a server, this is my code.
-(void) DownloadImages
{
NSLog(#"Images count to download %lu",(unsigned long)[productID count]); // about 3000
if ([productID count] > 0)
{
// Document Directory
NSString *myDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for (i = 0; i < [productID count]; i++)
{
#autoreleasepool // is this right of use autoreleasepool???
{
#autoreleasepool
{
[self performSelectorInBackground:#selector(UpdateUI) withObject:nil];
}
NSLog(#"image no %d", i);
NSString *imageURL = [NSString stringWithFormat:#"http://serverPath/%#/OnlineSale/images/products/%#img.jpg",clientSite,[productID objectAtIndex:i]];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
// Reference path
NSString *imagePath = [NSString stringWithFormat:#"%#/%#img.jpg",myDirectory,[productID objectAtIndex:i]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];
[imageData writeToFile:imagePath atomically:YES];
}
}
NSLog(#"Downloading images completed...");
}
}
EDIT: this is UpdateUI method
-(void) UpdateUI
{
spinner.hidden = NO;
[spinner startAnimating];
progressCounter.hidden = NO;
status.text = #"Synchronising Product Images...";
progressCounter.text = [NSString stringWithFormat:#"%d / %lu", i,(unsigned long)[productID count]];
}
while processing this, when i am in simulator the maximum memory used is:
on more than 1500 images download.
but testing on iPad, the maximum memory is reached to 106.9 MB and app crashed with this message, just after 500 images download:
and screen shows this message:
i am stuck on this point from last two days, after scratching my head don't find any solution. please help me on this issue..
Edit:
using autoreleasedpool way is right or not???
First of all, I would suggest enclosing the whole for loop block inside of #autoreleasepool. It would look like this:
for (i = 0; i < [productID count]; i++)
{
#autoreleasepool // is this right of use autoreleasepool???
{
[self performSelectorInBackground:#selector(UpdateUI) withObject:nil];
NSLog(#"image no %d", i);
NSString *imageURL = [NSString stringWithFormat:#"http://serverPath/%#/OnlineSale/images/products/%#img.jpg",clientSite,[productID objectAtIndex:i]];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
// Reference path
NSString *imagePath = [NSString stringWithFormat:#"%#/%#img.jpg",myDirectory,[productID objectAtIndex:i]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];
[imageData writeToFile:imagePath atomically:YES];
}
}
If what you tried to accomplish with your #autoreleasepool block was handling memory allocated in updateUI, that is not the right approach. You should instead enclose updateUI body inside of #autoreleasepool.
I would also suggest to post the content of updateUI; you could even comment that call out and see whether the download-only is causing the memory issue.
More generally, it seems pretty suspicious to me that you are updating the UI on a background thread. UI should be only updated on the main thread, as a general practice. (But without seeing updateUI definition, I cannot really say if this is ok or not).
About the different results you get with a device or the simulator, this is "normal". The simulator is totally unreliable when it come to memory handling, you could also detect leaks with the simulator that are not there on a device.
EDIT:
As an easy patch, try with this approach:
for (i = 0; i < [productID count]; i++)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self UpdateUI];
NSLog(#"image no %d", i);
NSString *imageURL = [NSString stringWithFormat:#"http://serverPath/%#/OnlineSale/images/products/%#img.jpg",clientSite,[productID objectAtIndex:i]];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
// Reference path
NSString *imagePath = [NSString stringWithFormat:#"%#/%#img.jpg",myDirectory,[productID objectAtIndex:i]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];
[imageData writeToFile:imagePath atomically:YES];
});
}
As you'll notice, I am dispatching each step in the loop individually. In this way, the mail loop will be able to update the UI. Notice that this implementation is not optimal, since dataWithContentsOfURL will take a little to execute (since it entails a network access) and should not be executed on the main thread (but you were doing it already like this). That should be dispatched on a background queue.
You should use more appropriate method for fetching Image data.
I suggest you to try NSURLDownload class.
Alternatively you could use one of popular 3rd party networking framework like AFNetworking

using GCD but my app is still freezing when loading images from server

I'm essentially loading images from the server every time a user taps the refresh button. However, this loading mechanism freezes the main thread, so I decided to try and use GCD to load the images in a different thread. I've looked at tutorials, and it seems that I'm setting it up right, but I'm still encountering the same issue.
My code:
-(IBAction)refreshButton:(id)sender{
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
[[API sharedInstance] commandWithParams:[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"stream",#"command",#"0",#"IdImage",_contentFilter,#"contentFilter",
nil]
onCompletion:^(NSDictionary *json){
NSMutableArray *tempArray = [json objectForKey:#"result"];
NSMutableArray *tempDataArray = [[NSMutableArray alloc] initWithCapacity:[_imagesTemporaryHolder count]];
NSLog(#"loaded temporary image holder");
for (int i=0;i<[_images count];i++) {
NSLog(#" populating _imagesData");
NSString *imageID = [[tempArray objectAtIndex:i] objectForKey:#"IdImage"];
NSString *imageURL = [NSString stringWithFormat:#"http://swiphtapp.com/Swipht/upload/%#.jpg",imageID];
NSData *tempImageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:imageURL]];
[tempDataArray insertObject:tempImageData atIndex:i];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
});
});
}
Could it be that I need to go to the API class and change something there? I'm using AFNNETWORKING by the way.
Thank you so much for the help!
UPDATE 1:
It looks like the API's "onCompletion" block is what's causing the problem...I did the NSLog(isMainThread) check in it, and it returns YES meaning that it's running in the main thread although it shouldn't be as I've encapsulated it in the GCD. Thoughts on how to solve this?
ANSWERED THANKS TO AWESOME PEOPLE
#eric
The completion block of the API call is meant to be performed on the main thread, because it's where you would normally update the views based on the data received. Try placing your GCD stuff INSIDE the completion block instead of encapsulating the whole thing in it. It's taking so long on the main thread because you are getting the NSData from the url returned from your original API call, which can take a long time (relatively speaking)
Basically the GCD needed to be in the API call's completion block.
Thanks again!
I believe this is the answer we came to in the comments under the initial post:
-(IBAction)refreshButton:(id)sender
{
[[API sharedInstance] commandWithParams:[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"stream",#"command",#"0",#"IdImage",_contentFilter,#"contentFilter",
nil]
onCompletion:^(NSDictionary *json){
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSMutableArray *tempArray = [json objectForKey:#"result"];
NSMutableArray *tempDataArray = [[NSMutableArray alloc] initWithCapacity:[_imagesTemporaryHolder count]];
NSLog(#"loaded temporary image holder");
for (int i=0;i<[_images count];i++) {
NSLog(#" populating _imagesData");
NSString *imageID = [[tempArray objectAtIndex:i] objectForKey:#"IdImage"];
NSString *imageURL = [NSString stringWithFormat:#"http://swiphtapp.com/Swipht/upload/%#.jpg",imageID];
NSData *tempImageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:imageURL]];
[tempDataArray insertObject:tempImageData atIndex:i];
}
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
});
});
}];
}

I want to give a priority each background thread and excute gradually

This code generates Low memory warning because background 5 thread is almostly loading images in same time.
I want to give a priority each thread and make lock & unlock.
I would make step by step thread. image 1 loading -> image2 loading -> image3 loading -> image4 loading.
How can I do this?
viewcontroller
-(void)viewDidLoad
{
for(int i=0; i<screenshotcount ; i++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString * url=[detailItem.mScreenshot objectAtIndex:i];
NSDictionary *args=[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:i], #"screenNum",
[NSString stringWithString:url],#"url",
nil];
[self performSelectorInBackground:#selector(loadImageScreenshot:) withObject:args];
[pool release];
}
}
loading image
-(void) loadImageScreenshot:(NSDictionary *) args
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage * screenshotImage=[UIImage imageWithStringURL:url];
NSDictionary *args2=[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:num], #"screenNum",
screenshotImage,#"image",
nil];
[self performSelectorOnMainThread:#selector(assignImageToScreenshotImageView:) withObject:args2 waitUntilDone:YES];
[pool release];
}
image add
- (void) assignImageToScreenshotImageView:(NSDictionary *)arg
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage * image= [arg objectForKey:#"image"];
UIImageView *imageview=[UIImageView alloc]init];
.
.
imageview.image=image;
[self.mScreenshotSpace addSubview:imageview];
[imageview release];
[pool release];
}
image from url
+(UIImage *)imageWithStringURL:(NSString *)strURL
{
NSURL *url =[NSURL URLWithString:strURL];
NSData * data=[[NSData alloc]initWithContentsOfURL:url options:NSDataReadingUncached error:&error];
UIImage * image=[UIImage imageWithData:data ];
[data release];
return image;
}
maybe I misunderstood your question, but from what you said, what you really want is to "serialise" the threads, that is, ensure they are executed one after the other. If this is the case I do not see a big advantage in having five (or more) threads if they spend most of the time waiting in a sort of "thread queue" :)
My 2 cents: Instead of playing with the threads' priority, maybe you should think about re-design the code to have a queue of files/images to load and a thread that dequeue and load the images one after the other. (the classic consumer/producer scenario)
If you need to speed up things you might think to have another thread doing some pre-fetching (if that make sense in your sw design/architecture)
CiaoCiao
Sergio
Sergio is right - if you can look at GCD and serialized queues, it shouldn't be too hard to migrate to it. Take a look at the videos from Apple for good how-to steps.

Resources