Multiple webservice Making UI unresponsive - ios

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

Related

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

IOS Application Loading Data From Web

I am trying to load data from web with few simple steps:
NSData *JSONData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"******"]];
NSObject *json = [JSONData objectFromJSONData];
NSArray *arrayOfStreams = [json valueForKeyPath:#"programs"];
NSDictionary *stream = [arrayOfStreams objectAtIndex:0];
NSString *str = [[NSString alloc]initWithString:[stream valueForKey:#"image"]];
NSURL *urlForImage1 = [NSURL URLWithString:str];
NSData *imageData1 = [NSData dataWithContentsOfURL:urlForImage1];
_screenForVideo1.image = [UIImage imageWithData:imageData1];
But the problem is I am doing 30 of this right after my application launches...
I want to load about 5 of them, and than load others. Because when I try to load all of them at the same time, my app is not launching all of them loaded...
Is there any way that I can load first few of them, and wait, and than load others?
As for loading them, you should probably display a spinner, start loading the image in background and then replace the spinner with the image once it’s ready.
- (void) viewDidLoad {
UIActivityIndicator *spinner = …;
[self.view addSubview:spinner];
[self performSelectorInBackground:#selector(startLoadingImage)
withObject:nil];
}
- (void) startLoadingImage {
// You need an autorelease pool since you are running
// in a different thread now.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [UIImage imageNamed:#"foo"];
// All GUI updates have to be done from the main thread.
// We wait for the call to finish so that the pool won’t
// claim the image before imageDidFinishLoading: finishes.
[self performSelectorOnMainThread:#selector(imageDidFinishLoading:)
withObject:image waitUntilDone:YES];
[pool drain];
}
- (void) imageDidFinishLoading: (UIImage*) image {
// fade spinner out
// create UIImageView and fade in
}

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
});
});
}];
}

app slowing down due to image load issue

I am having problem with the loading of content in the app, I see that the data is fetched by the app but the images take lot of time to load, is there any possibility to load images afterwords. The code is below:
NSDictionary *dict=[discussionArray objectAtIndex:indexPath.row];
UIImageView *avatarimage = (UIImageView *)[cell viewWithTag:4];
NSString *photoStrn=[dict objectForKey:#"photo"];
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSString *u=[NSString stringWithFormat:#"http://%#",photoStrn];
NSURL *imageURL=[NSURL URLWithString:u];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_sync(dispatch_get_main_queue(), ^{
UIImage *dpImage = [UIImage imageWithData:imageData];
if (dpImage==nil)
{
dpImage = [UIImage imageNamed:#"profileImage.png"];
}
avatarimage.image = dpImage;
});
If you want more details I will provide :)
You can use GCD for doing this:
dispatch_async(dispatch_get_global_queue(0,0), ^{
for (NSDictionary *dic in discussionArray)
{
NSString *photoStr=[dic objectForKey:#"photo"];
NSString * photoString=[NSString stringWithFormat:#"http://%#",photoStr];
UIImage *dpImage = [UIImage imageWithData: [NSData dataWithContentsOfURL:[NSURL URLWithString:photoString]]];
if (dpImage==nil)
{
dpImage = [UIImage imageNamed:#"profileImage.png"];
}
}
});
Get SDWebImage Here and add that in your project and include
UIImageView+WebCache.h
in class implementation file
UIImageView *imag=[[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 40, 40)];
[imag setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",[[jsonarray objectAtIndex:indexPath.row]valueForKey:#"imgurl"]]] placeholderImage:[UIImage imageNamed:#"Icon#2x.png"]];
[self.view addSubview:imag];
[imag release];
SDWebImage will be more useful for loading images from URL.
Hope this Helps !!!
James,
With this line,
[NSData dataWithContentsOfURL:[NSURL URLWithString:photoString]]]
you make a synchronous network call on the main thread. The current thread will hang untile the network call is complete.
The solution would be to do an asynchronous network call. The AFNetworking library provides a great category to load images asynchronously : UIImageView+AFNetworking.

Easy load images asynchronously in a UITableView using GCD

After two days searching this answer I found a solution.
Use GCD for download images asynchronously.
Use NSMutableDictionary for save images in memory.
I found the solution explained here by Duncan C:
http://iphonedevsdk.com/forum/iphone-sdk-development/104438-grand-central-dispatch-tableview-images-from-the-web.html
How to implement:
- (void)viewDidLoad
{
(...)
dispatch_queue_t mainQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
imagesDictionary = [[NSMutableDictionary alloc] init];
(...)
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
(...)
NSData *imageData = [imagesDictionary objectForKey:#"IMAGE URL"];
if (imageData)
{
UIImage* image = [[UIImage alloc] initWithData:imageData];
imageFlag.image = image;
NSLog(#" Reatriving ImageData...: %#", #"IMAGE URL");
}
else
{
dispatch_async(mainQueue, ^(void) {
NSString *url = #"IMAGE URL";
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
imageFlag.image = image;
[imagesDictionary setObject:imageData forKey:#"IMAGE URL"];
NSLog(#" Downloading Image...: %#", #"IMAGE URL");
});
}
(...)
}
The project in GitHub: https://github.com/GabrielMassana/AsynchronousV2.git
I know that if the project is large and with a lot of cells I can go run out of memory. But I think this solution is a nice approach for a newbie.
What do you think about the project?
If the project is really large, the best option is to save the images in disk? But then the problem is the possibility to go run out of memory in the disk, isn't it? Maybe, then, we need a mechanism to delete all the images from disk.
Use NSCache to hold your downloaded images instead of NSDictionary. This will manage itself in low memory situations and remove items that haven't been accessed for a while. In that situation, they will be loaded from the URL again if needed.

Resources