UIImage passing between controllers causes memory warnings - ios

I am working on an app that takes an image from the photo library using UIPickerController and then loads it into an UIImageView in the first controller. Then i pass the UIImage via property to the second controller of navigation controller in order to edit the image here. When i pop this EditController tapping on Back and then retake the process passing one more time the image to the EditController, the memory encreases every time and after 5 cycles i receive memory warnings. Is there a solution to this issue?
//Getting the image from the PickerController
_imageToEdit = info[UIImagePickerControllerOriginalImage];
_imageToEdit=[self fixOrientation:_imageToEdit];
[_imageView setImage:_imageToEdit];
//Passing image to EditController
if ([segue.identifier isEqualToString:#"editImage"]) {
EditImageViewController *destViewController=segue.destinationViewController;
destViewController.newlyImage=_imageToEdit;
destViewController.optVC=self;
destViewController.numberReceived=_firstTimeInEdit;
}

I made a new method where I release all the objects I don't need. I don't receive memory warnings anymore.
- (void) backAction {
[_editBaseController removeAllButtons];
_editBaseController = nil;
_undoImage = nil;
copyImage = nil;
_modifiedImage = nil;
_newlyImage = nil;
aCIImage = nil;
context = nil;
free(imageRawData);
free(filterChannelRowData);
_scrollingViewOne = nil;
_scrollingViewTwo = nil;
[self.navigationController popViewControllerAnimated:YES];}

Related

Using autoreleasepool for objects created in another method

I am needing to use #autoreleasepool because I Am getting some high memory usage. The issue is, the objects that are taking up memory are created in one method on a background thread, then sent to a second method where they are used to update the UI.
- (void)createObjects {
TestObject = *test = [[TestObject alloc] init];
[self updateUIWithTestObject:test];
}
- (void)updateUIWithTestObject:(TestObject *)testObject {
// Update UI
self.textLabel.text = testObject.text;
}
Where would I be able to place the #autoreleasepool so that I can release the objects that I have created?

How to check NSCache instance value set or not

I have an instance of NSCache, like: NSCache *imageCache; It is basically used for holding some remote "image" values with different "#keys". I alloc & init NSCache in a Global class at the beginning and set a if else condition, like this:
if (self.imageCache == nil)
{
self.imageCache = [[NSCache alloc] init];
}
else
{
NSLog(#"cache set");
}
I #import that "Global Class" in all of ViewControllers, so that I don't have to parse images every time. But the problem is when I go to other ViewControllers It seems like, NSCache alloc & init every time. Because It takes same time to load the images as 1stVC. I think the if else condition didn't working perfectly or it's not the appropriate way to check either NSCache set or not.
Can anyone tell me whats wrong with it? One thing more, The imageCache is used from a global variable.
Thanks in advance.
Have a good day.
ADDITION:
This is the method where I load the UIButtons in UIScrollView as subView. This is a UIViewClass which I add in my "EveryViewController" as a subView Just take have a look on the if (cachedImage) line. It works fine. But when I want to check either the NSCache (iADImageCache) set or not, it shows me it's not set. But which should be set. In this situation how can I check all several iADImageCache with their different #"Key" name?
Thanks again.
-(void) loadUIButton
{
[self loadScrollView];
for (int i = 0; i < [iADDisplayArray count]; i++)
{
adButtonOutLet = [[UIButton alloc] initWithFrame:CGRectMake(i*320, 0, ButtonWidth, ButtonHight)];
currentAd = [iADDisplayArray objectAtIndex:i];
NSString *path = currentAd.bannerIconURL;
NSURL *url = [NSURL URLWithString:path];
NSMutableURLRequest *requestWithBodyParams = [NSMutableURLRequest requestWithURL:url];
NSData *imageData = [NSURLConnection sendSynchronousRequest:requestWithBodyParams returningResponse:nil error:nil];
UIImage *originalImage = [UIImage imageWithData:imageData];
UIImage *cachedImage = [self.iADImageCache objectForKey:currentAd.bannerIconURL];
if (cachedImage)
{
[adButtonOutLet setImage:cachedImage forState:UIControlStateNormal];
//NSLog(#"OnecachedImage %#", cachedImage);
}
else
{
[self.iADImageCache setObject:originalImage forKey:currentAd.bannerIconURL];
[adButtonOutLet setImage:originalImage forState:UIControlStateNormal];
NSLog(#"OneimageCache %#", self.iADImageCache);
}
adButtonOutLet.userInteractionEnabled= YES;
[adButtonOutLet setTag:i];
[adButtonOutLet addTarget:self action:#selector(goToURL:) forControlEvents:UIControlEventTouchUpInside];
[self.iADScrollView addSubview:adButtonOutLet];
}
}
You could make a singleton class, instance at application did finish launching and use it wherever you are, through all the view controllers. In the singleton class put a property
#property(nonatomic,strong) NSCache* imageCache;
then instance it just once in the singleton class init method. In this way you don't have to care about it, and you can just add images to that cache. Of course you have to check if the image is cached or not based on the existance of a key inside that cache.
NSCache* globalCache = [SingletonClass sharedInstanceMethod].imageCache;
UIImage *imageX = [globalCache objectForKey: #"keyX"];
if (!imageX) {
// download your image
imageX = <Download method>;
[globalCache setObject: imageX forKey: #"keyX"];
}
// Do your stuff (like showing the image)
....
...
..
.
Hope it helps
Unless actually needed across the whole program, I would suggest another approach that would restrict the caching to the class that actually needs to use it by adding the class method:
+ (NSCache *)staticCacheForClass
{
static NSCache *staticCache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
staticCache = [NSCache new];
staticCache.name = #"myCache";
});
return staticCache;
}
Using this - you restrict the changes to the cache to be internal to the class that uses it and avoid creating another singleton. The cache is also preserved across creating and destroying instances of this class.
That said, a singleton could be a viable solution if you need to access the cache from different classes, since a cache singleton is not really a global state.
Cheers.

UITableViewCell image load from url

I have a problem with loading an image from an url to display in a table. I currently have the following code to handle the image loading in a class that extends UITableViewCell:
- (void) initWithData:(NSDictionary *) data{
NSDictionary *images = [data objectForKey:#"images"];
__block NSString *poster = [images objectForKey:#"poster"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSURL *posterURL = [[NSURL alloc] initWithString:poster];
NSData *imageData = [NSData dataWithContentsOfURL:posterURL];
if (imageData != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
// 4. Set image in cell
self.backgroundImage.image = [UIImage imageWithData:imageData];
[self setNeedsLayout];
});
}
});
self.backgroundImage.image = [UIImage imageNamed:#"static"];
}
The initWithData method is called from the ViewController in the tableView:cellForRowAtIndexPath: delegate. Everything works as expected until i scroll. From what i read, the TableView cells are recycled and because the images are being loaded async, i get rows with wrong images. Also, the images are not cached and are loaded again whenever the cell is displayed.
Eg: Scroll to the middle and immediately scroll back up. The first cell will have the image that's corresponding to the middle cell that didn't get to finish loading.
Any help or suggestions? Thank you very much :)
First of all as the comment mentioned, I would definitely recommend using an existing framework/component to do this job.
The best candidates are probably:
https://github.com/rs/SDWebImage
https://github.com/enormego/EGOImageLoading
OR if you also want a general networking library
https://github.com/AFNetworking/AFNetworking
That said, if you still want to try it on your own, you would probably want to implement caching with an NSMutableDictionary using the indexPath as the key, and the image as the value.
Assuming you have an initialized instance variable NSMutableDictionary *imageCache
In your cellForRowAtIndexPath method, before attempting to do any image loading, you would check to see if your cache already has an image for this index by doing something like this
if(! imageCache[indexPath])
{
// do your web loading here, then once complete you do
imageCache[indexPath] = // the new loaded image
}
else
{
self.backgroundImage.image = imageCache[indexPath];
}

Animation Using To Much Memory iOS7

I have a series of .png images loaded into an animation, which works fine, but when I switch to another view, the memory that the animation uses is not freed up. Which takes up more and more memory every time you return to that view until eventually the app quits due to too much memory pressure.
Is there anything I can put in view diddisappear or another method to free this memory from the animation when the view is changed?
Also this only seems to happen in with my 4S running iOs 7. On my 4S with 6.1.2 it runs smoothly.
NSArray *animationArray = [NSArray arrayWithObjects:
[UIImage imageNamed:#"0001.png"],
[UIImage imageNamed:#"0002.png"],
[UIImage imageNamed:#"0003.png"],
...
nil];
self->tapanimation1.animationImages =animationArray;
self->tapanimation1.animationDuration = .5;
self->tapanimation1.animationRepeatCount = 1;
[self->tapanimation1 startAnimating];
How you alloc image in animation --- there are two ways of intializing UIImage
First is -->
[UIImage imageNamed:#"kshitij.png"];
There is issue with this method it doesnot dealloc memory , even on release .
Second is -->
If image is in your app Bundle,then always use this
NSString *filePath = [[NSBundle mainBundle]pathForResource:#"kshitij" ofType:#"png"];
UIImage *image = [[UIImage alloc]initWithContentsOfFile:filePath];
Now use this image and make it release too , it will surely will save your some MB.
and you can also use ARC for memory saving.
Try this code --
NSSMutableArray *imageNameArray = [[NSSMutableArray
alloc]initWithObjects:#"0001",#"0002",nil];
NSMutableArray *imageArray = [[NSMutableArray alloc] init];
for(int i=0;i<imageNameArray.count;i++)
{
NSString *filePath = [[NSBundle mainBundle]pathForResource:[imageNameArray
objectAtIndex:i] ofType:#"png"];
UIImage *image = [[UIImage alloc]initWithContentsOfFile:filePath];
[imageArray addObject:image];
}
self->tapanimation1.animationImages = imageArray;
self->tapanimation1.animationDuration = .5;
self->tapanimation1.animationRepeatCount = 1;
[self->tapanimation1 startAnimating];
Now when animation does stop just release array. If you are using ARC then make it nil.
How are you navigating to your new view controller, and how are you getting back? Are you using an unwind segue? Are you presenting the new view controller as a modal and then dismissing it?
Or are you using a segue to link to the second VC, and then another segue to link back? If you are using a second (non-unwind) segue to link back, then that's your problem. That would cause you to create a new instance of your first view controller each time.
As for releasing memory, you could certainly set your view's animationImages array to inil in viewDidDisappear, and then re-load the images in viewWillAppear, but it doesn't make sense that you memory footprint would go up with each round trip to the second view controller. That indicates a problem.

Am I missing something with ARC?

I'm developing a large scale application for iOS 5 using ARC in xcode. The system seems to work well except for when I'm trying to deallocate one of my interfaces. I'm using a framework called WhirlyGlobe to create a 3D interactive globe in the first view controller.
When I switch view controllers (between the 4 I have), I notice that the memory being used for the view controller with the globe isn't being released. All the other view controllers (only using simple views and images) release their memory fine - But the globe stays resident, or so it seems. When navigating back to the globe, I get almost a 10mb jump in memory due to 1mb allocations in "glsmLoadTextureLevelBuffer".
To get on with my question - Is there anything more I can do, with ARC active, to help release my objects? I've noticed my viewDidUnload and dealloc methods are not being called at all, and that the only way I can get anything to fire is using viewDidDisappear (which is not ideal obviously) - See below:
- (void)clear
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (self.layerThread)
{
[self.layerThread cancel];
while (!self.layerThread.isFinished)
[NSThread sleepForTimeInterval:0.001];
}
self.glView = nil;
self.sceneRenderer = nil;
if (theScene)
{
delete theScene;
theScene = NULL;
}
self.theView = nil;
self.texGroup = nil;
self.layerThread = nil;
self.earthLayer = nil;
self.vectorLayer = nil;
self.labelLayer = nil;
self.interactLayer = nil;
self.pinchDelegate = nil;
self.panDelegate = nil;
self.tapDelegate = nil;
self.longPressDelegate = nil;
self.rotateDelegate = nil;
}
- (void)viewDidDisappear:(BOOL)animated {
NSLog(#"dealloc - viewDidDisappear");
[self clear];
}
I'm setting everything I no longer need to nil. Is this the best practise?
The globe setup code:
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// Set up an OpenGL ES view and renderer
EAGLView *ev = [[EAGLView alloc] initWithFrame:CGRectMake(0, 0, 824, self.view.frame.size.height)];
self.glView = ev;
self.sceneRenderer = [[SceneRendererES1 alloc] init];
UIColor *whiteC = [UIColor whiteColor];
[sceneRenderer setClearColor:whiteC];
glView.renderer = sceneRenderer;
glView.frameInterval = 2; // 60 fps (2)
[self.view addSubview:glView];
self.view.backgroundColor = [UIColor blackColor];
self.view.opaque = YES;
self.view.autoresizesSubviews = YES;
//glView.frame = self.view.bounds;
glView.frame = CGRectMake(275, GLOBE_HEIGHT_FIX, 768, SCREEN_HEIGHT+STATUS_BAR_HEIGHT); // was 260 x
glView.backgroundColor = [UIColor whiteColor];
self.view.backgroundColor = [UIColor whiteColor]; // red for debug
// Create the textures and geometry, but in the right GL context
[sceneRenderer useContext];
self.texGroup = [[TextureGroup alloc] initWithInfo:[[NSBundle mainBundle] pathForResource:#"bdGlobe_info" ofType:#"plist"]];
// Need an empty scene and view
theScene = new WhirlyGlobe::GlobeScene(4*texGroup.numX,4*texGroup.numY);
self.theView = [[WhirlyGlobeView alloc] init];
[theView setFarPlane:5.0];
[theView setHeightAboveGlobe:GLOBE_HEIGHT_VIEW];
if (globeShouldAnimate) glView.alpha = 1.0;
// Need a layer thread to manage the layers
self.layerThread = [[WhirlyGlobeLayerThread alloc] initWithScene:theScene];
// Earth layer on the bottom
self.earthLayer = [[SphericalEarthLayer alloc] initWithTexGroup:texGroup];
[self.layerThread addLayer:earthLayer];
// Set up the vector layer where all our outlines will go
self.vectorLayer = [[VectorLayer alloc] init];
[self.layerThread addLayer:vectorLayer];
// General purpose label layer.
self.labelLayer = [[LabelLayer alloc] init];
[self.layerThread addLayer:labelLayer];
self.interactLayer = [[InteractionLayer alloc] initWithVectorLayer:self.vectorLayer labelLayer:labelLayer globeView:self.theView
countryShape:[[NSBundle mainBundle] pathForResource:#"10m_admin_0_map_subunits" ofType:#"shp"]
oceanShape:[[NSBundle mainBundle] pathForResource:#"10m_geography_marine_polys" ofType:#"shp"]
regionShape:[[NSBundle mainBundle] pathForResource:#"10m_admin_1_states_provinces_shp" ofType:#"shp"]];
self.interactLayer.maxEdgeLen = [self.earthLayer smallestTesselation]/10.0;
[self.layerThread addLayer:interactLayer];
// Give the renderer what it needs
sceneRenderer.scene = theScene;
sceneRenderer.view = theView;
// Wire up the gesture recognizers
self.panDelegate = [PanDelegateFixed panDelegateForView:glView globeView:theView];
self.tapDelegate = [WhirlyGlobeTapDelegate tapDelegateForView:glView globeView:theView];
self.longPressDelegate = [WhirlyGlobeLongPressDelegate longPressDelegateForView:glView globeView:theView];
// Kick off the layer thread
// This will start loading things
[self.layerThread start];
You can use the allocations instrument for this. Using heap shot, you can mark the heap at various points in the lifetime of your application and compare the object graph that constitutes the current allocations in memory at the point of each snapshot. That should help you narrow down what's being retained and by whom.
This is anecdotal, but COULD be a similar situation.
I just had a situation where my objects were not getting released ever, in ARC, even though they were accessed in a static way (e.g., [SingletonThing instance].thing) because the autorelease pool wasn't draining. The reason for this was a bizarre endless loop that runs on its own thread. Putting a separate #autorelease block for the enclosing code.
On the other hand, even if one of your (or the libs) object has the UIView as a subview, I think your UIViewController will never viewDidUnload and therefore never dealloc. I have to check this empirically.
make sure your -(void)viewDidDisappear:(BOOL)animated is invoked.
if you use
[self.view addSubview:yourViewController.view];
and
[yourViewController.view removeFromSuperview];
then viewDidDisappear: and viewDidAppear: will not be invoked
these callback will only be invoked
when you use presentViewController: in IOS 5
or presentModalViewController:
and dismissViewControllerAnimated: in IOS 5
or dismissModalViewControllerAnimated:
or use UINavigationController to present and dismiss your viewController
I found the problem after using heap shots (thanks to Mark Adams) - Turns out I wasn't making a couple of delegates weak entities, so they weren't being released when changing the view controller. Default strong delegates stay resident.
Thanks to all the suggestions, they all helped point me in the right direction :)

Resources