Got memory pressure notification (critical/non-critical) - iOS objective-c - ios

Got memory pressure notification (critical/non-critical).
ā€œnā€ numbers of images loading from API in tableview. But the app is crashing with above mentioned error after few seconds.
The current code is,
[post_img sd_setImageWithURL:[NSURL URLWithString:[arr objectAtIndex:i]] placeholderImage:[UIImage imageNamed:#"placeHolder.jpg"] options:SDWebImageRefreshCached];
I tried remove cache,
[[SDImageCache sharedImageCache] removeImageForKey:[arr objectAtIndex:i] fromDisk:YES withCompletion:nil];
Also, I tried to fetch images using dispatchqueue,
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: [arr objectAtIndex:i]]];
if ( data == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
post_img.image = [UIImage imageWithData: data];
});
});
But still I getting the error.

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

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

Loading takes a while when i set UIImage to a NSData with a url.

NSData *imageUrl = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageUrl"] ]];
cell.thumbnailImageView.image=[UIImage imageWithData:imageUrl];
this is how i use imageUrl to load them into UIImage but it takes a while to load and the program seems like it crashed or entered to an infinite loop.
How can i make the content of UIImage with url but faster?
I would suggest you to use AsyncImageView a beautiful implementation by Nicklockwood -father of iCarousel.
Link
it is very simple to use.
#import "AsyncImageView.h"
and in all imageViews do this.
[imageView setImage:#"default.png"];
[imageView setImageURL:#"whateverurl/whateverimage.png"];
In your case it would be:
[cell.thumbnailImageView setImageURL:#"yourURL"];
It works like a charm, and my code is in production. But if you still want your code to work try this:
UIActivityIndicator *activity =[[UIActivityIndicator alloc] initWithStyle:UIActivityIndicatorWhite];
[activity setFrame:CGRectMake(0,0,30,30)];
[cell.contentView addSubview:activity];
cell.thumbnailImageView.image=[UIImage imageNamed:#"Default~cell~image.png"];
dispatch_queue_t dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(dispatchQueue, ^(void)
{
[activity startAnimating];
[self loadImages];
dispatch_sync(dispatch_get_main_queue(), ^{
NSData *imageUrl = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageUrl"] ]];
cell.thumbnailImageView.image=[UIImage imageWithData:imageUrl]; [activity stopAnimating];
[activty setHidden:YES];
});
});
This happens because every time your cell goes off screen your image is released and if you scroll it back on screen you will have to download your image again. Simply cache your image when downloaded and check if exists use it if not download from internet. You can use third party library like SDWebImage GitHub link
Import SDWebImageView+WebCache.h and in your cellForRowAtIndexPath use the following
[cell.thumbnailImageView setImageWithURL:[NSURL URLWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageUrl"] placeholder:[UIImage imageNamed:#"placeholder.png"]];
use sdwebimage and down load the library file from here
the few steps you do follow
add the sdwebimage in your project after that
in your .h file
#import "UIImageView+WebCache.h"
in your .m file
call the single line in your cellforRowAtIndexPath
[cell.thumbnailImageView setImageWithURL:[NSURL URLWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageUrl"]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Use an NSOperationQueue so that you can perform the loading of the data on a background thread.
After that you should set the image on the main queue.
// Assume queue is created
[queue addOperationWithBlock:^{
NSData *imageUrl = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageUrl"] ]];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.thumbnailImageView.image=[UIImage imageWithData:imageUrl];
}];
}];
For more about NSOperationQueue see the docs about NSOperationQueue.
So try to load image asynchronous.
https://github.com/nicklockwood/AsyncImageView
Use gcd to populate the images,
NSURL *imageUrl = [NSURL URLWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageUrl"] ]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
UIImage *image = nil;
image = [UIImage imageWithData: [NSData dataWithContentsOfUrl: imageUrl]];
dispatch_sync(dispatch_get_main_queue(), ^{
cell.thumbnailImageView.image = image;
});
})
You will probably want to store the image to some model object, also track if the download has been initiated, since you would not want to queue up the request for the same image url.

Memory pressure downloading many images

I am trying to download thousands of pictures (Maximum 350 Kb each) from the server, but I go by over a thousand images I receive the alert "Memory Presure".
Basically I have an array with all the names of the images and do a loop to bring one to one like this:
for (int x=0; x<unique.count; x++) {
NSURL *ImageLink = [NSURL URLWithString:[NSString stringWithFormat:#"http://urltoimagesfolder.com/", [unique objectAtIndex:x]]];
NSData *data = [NSData dataWithContentsOfURL:ImageLink];
UIImage *img = [[UIImage alloc] initWithData:data];
if (data.length !=0) {
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[unique objectAtIndex:x]]; //add our image to the path
[UIImageJPEGRepresentation(img, 1.0) writeToFile:fullPath atomically:YES];
//[self saveImage:img :NombreFoto];
//[self Miniatura:img :[NSString stringWithFormat:#"mini-%#", [unique objectAtIndex:x]]];
}
data = nil;
img = nil;
}
Question: How I can download all the images without the app crash with memory pressure?
UIImageJPEGRepresentation() may cause the memory overflow.
But you don't need to use that function, you can check if the received data is image and write its bytes directly to disk via sending message writeToFile: to data object.
You can fix you code like this:
for (int x=0; x<unique.count; x++) {
NSURL *ImageLink = [NSURL URLWithString:[NSString stringWithFormat:#"http://urltoimagesfolder.com/", [unique objectAtIndex:x]]];
NSData *data = [NSData dataWithContentsOfURL:ImageLink];
if (data.length !=0) {
UIImage *img = [[UIImage alloc] initWithData:data];
if (img) {
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[unique objectAtIndex:x]]; //add our image to the path
[data writeToFile:fullPath atomically:YES];
}
img = nil;
}
data = nil;
}
However this is not the optimal solution. -dataWithContentsOfURL: is synchronous method and will stop execution of your main thread while downloading the file. As a consequence the UI will hang during the download. To make your UI not to hang you can use asynchronous url requests.
See -sendAsynchronousRequest:queue:completionHandler: method of the NSURLConnection class. Or if your app is only for iOS 7 see -dataTaskWithURL:completionHandler: as alternative.

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.

Resources