We all know about the mysterious behind-the-scenes caching mechanism of UIImage's imageNamed: method. In Apple's UIImage Class Reference it says:
In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. This purging behavior affects only the image data stored internally by the UIImage object and not the object itself. When you attempt to draw an image whose data has been purged, the image object automatically reloads the data from its original file. This extra load step, however, may incur a small performance penalty.
In fact, image data will not be "purged from a UIImage object to free up memory on the system" as the documentation suggests, however. Instead, the app receives memory warnings until it quits "due to memory pressure".
EDIT: When using the conventional image file references in your Xcode project, the UIImage caching works fine. It's just when you transition to Asset Catalogs that the memory is never released.
I implemented a UIScrollView with a couple of UIImageViews to scroll through a long list of images. When scrolling, the next images are being loaded and assigned to the UIImageView's image property, removing the strong link to the UIImage it has been holding previously.
Because of imageNamed:'s caching mechanism, I quickly run out of memory, though, and the app terminates with around 170 MB memory allocated.
Of course there are plenty of interesting solutions around to implement custom caching mechanisms, including overriding the imageNamed: class method in a category. Often, the class method imageWithContentOfFile: that does not cache the image data is used instead, as even suggested by Apple developers at the WWDC 2011.
These solutions work fine for regular image files, although you have to get the path and file extension which is not quite as elegant as I would like it to be.
I am using the new Asset Catalogs introduced in Xcode 5, though, to make use of the mechanisms of conditionally loading images depending on the device and the efficient image file storage. As of now, there seems to be no straight forward way to load an image from an Asset Catalog without using imageNamed:, unless I am missing an obvious solution.
Do you guys have figured out a UIImage caching mechanism with Asset Catalogs?
I would like to implement a category on UIImage similar to the following:
static NSCache *_cache = nil;
#implementation UIImage (Caching)
+ (UIImage *)cachedImageNamed:(NSString *)name {
if (!_cache) _cache = [[NSCache alloc] init];
if (![_cache objectForKey:name]) {
UIImage *image = ???; // load image from Asset Catalog without internal caching mechanism
[_cache setObject:image forKey:name];
}
return [_cache objectForKey:name];
}
+ (void)emptyCache {
[_cache removeAllObjects];
}
#end
Even better would of course be a way to have more control over UIImage's internal cache and the possibility to purge image data on low memory conditions as described in the documentation when using Asset Catalogs.
Thank you for reading and I look forward to your ideas!
UPDATE: Cache eviction works fines (at least since iOS 8.3).
I am running into the same issue (iOS 7.1.1) and I kind of though that #Lukas might be right
There is a high probability that the mistake is not inside Apple's ... caching but in your .. code.
Therefore I have written a very simple Test App (view full source below) where I still see the issue. If you see anything wrong with it, please let the me know about it. I know that it really depends on the image sizes. I only see the issue on an iPad Retina.
#interface ViewController ()
#property (nonatomic, strong) UIImageView *imageView;
#property (nonatomic, strong) NSArray *imageArray;
#property (nonatomic) NSUInteger counter;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageArray = #[#"img1", ... , #"img568"];
self.counter = 0;
UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
self.imageView = [[UIImageView alloc] initWithImage: image];
[self.view addSubview: self.imageView];
[self performSelector:#selector(loadNextImage) withObject:nil afterDelay:1];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
NSLog(#"WARN: %s", __PRETTY_FUNCTION__);
}
- (void)loadNextImage{
self.counter++;
if (self.counter < [self.imageArray count])
{
NSLog(#"INFO: %s - %lu - %#",
__PRETTY_FUNCTION__,
(unsigned long)self.counter,
[self.imageArray objectAtIndex:self.counter]);
UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
[self.imageView setImage:image];
[self performSelector:#selector(loadNextImage) withObject:nil afterDelay:0.2];
} else
{
NSLog(#"INFO: %s %#", __PRETTY_FUNCTION__, #"finished");
[self.imageView removeFromSuperview];
}
}
#end
Inplace Implementation
I wrote some code to keep the image asset but load it with imageWithData: or imageWithContentsOfFile: use xcassets without imageNamed to prevent memory problems?
Related
i have iPhone application. In some cases, when the device is getting low on free memory, some actions (for example, opening the camera) might cause the application to crash.
My question is that:
I want to prevent these crashes, what is the common way applications
do such thing (blocking specific actions, notifying the user, other
ideas)? I ask because i didn't encountered such behaviour in iOS
applications i ran into.
Are there any ways of preventing such crashes and remain full app functionality, such as iOS system calls to free more memory and etc.? if anyone has best practice or good heuristic i would love to hear about it.
EDIT: I ask this question assuming i already implement the 'didReceiveMemoryWarning' function and freed all the memory i can.
EDIT 2: my app is about pictures. A lot like camera scanner apps, this app allows taking pictures, image processing and saving data about them in memory. my crashes usually happens when i scan a lot of pictures.
Some thumb rules i follow:
Using Arc
Use weak for iboutlets (except top level example: UIwindow) and for delegates
Use Strong for class properties and copy for NSString.
Dont access variables directly, use self....way.
Dont use autorelease way of creating new objects, example NSArray *array = [NSArray arrayWithObjects......., instead use NSArray *array = [NSArray alloc] initWit....
Same way for NSString class. try to use [NSString alloc] initWithFormat..... instead of [NSString stringWithFormat.
When ever you are adding NSNotification(addObserver...) centre must remove(removeObserver..) them in dealloc.
Implement didReceiveMemoryWarning(view controller level) or applicationDidReceiveMemoryWarning(application level and it is called first than view controller level) properly, how ever there are times when you only and only wish to save from crash.you can display an alert telling user less memory available, you can pop/present ..user to home screen.(Bad practice).
Dont perform any manipulation on main thread while being in background thread.Always use #autorelease block for background threads.
use GCD/NSOperation queue for long running processes.
Keep an sharp eye on image resources you are using, use image only of desired size not scale big image to small image size for your need.
USE autorelease pool for long running loops, which create a lot of autoreleased objects.
i have some code snippet for you which ypu can follow:
//way 1 all on main thread bad approach, basically we are just doing some image manipulation on main thread(should not do on main thread :))
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
YourApplicationDelegate *appDelegate = (YourApplicationDelegate *)[[UIApplication sharedApplication]delegate];
[appDelegate showLandscapeLoading];//think it as progress view/loader
UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *imageData = UIImagePNGRepresentation(pickedImage);
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"category_imagename.jpeg"];
NSError * error = nil;
//from here
[imageData writeToFile:path options:NSDataWritingAtomic error:&error];
**//the important part for discussion UI manipulation on main thread bad bad bad**
CGSize size1;//A
size1.width = 400;
size1.height = 400;
UIGraphicsBeginImageContext(size1);
[pickedImage drawInRect:CGRectMake(0, 0, size1.width, size1.height)];
UIImage *bigImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSString *bigThumb = [documentsDirectory stringByAppendingPathComponent:#"category_thumb_imagename.jpeg"];
NSData *data1=UIImageJPEGRepresentation(bigImage, 0.5);
BOOL status1=[data1 writeToFile:bigThumb atomically:YES];
**//up to here should be in non ui thread/seperate thread**
**//below code should go in main thread**
NSLog(#"status1 -> %d",status1);
[self setCategoryImageName:bigImage];
[self.imgCategory setImage:pickedImage];
if (status1) {
isAddingCategoryImage = YES;
}
[appDelegate stopLandscapeLoading];
if (error != nil) {
NSLog(#"Error: %#", error);
return;
}
if ([self.popoverController isPopoverVisible]) {
[self.popoverController dismissPopoverAnimated:true];
}
[picker.view removeFromSuperview];
}
The correct way:
Using NSOperation:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
YourApplicationDelegate *appDelegate = (YourApplicationDelegate *)[[UIApplication sharedApplication]delegate];
[appDelegate showLandscapeLoading];
UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSError * error = nil;
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperationWithBlock:^
{
// Create a graphics image context very slow stuff
CGSize newSize = CGSizeMake(400, 400);
UIGraphicsBeginImageContext(newSize);
// Tell the old image to draw in this new context, with the desired
// new size
[pickedImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
NSString *bigThumb = [documentsDirectory stringByAppendingPathComponent:#"category_thumb_imagename.jpeg"];
NSData *data1=UIImageJPEGRepresentation(newImage, 0.5);
BOOL status1=[data1 writeToFile:bigThumb atomically:YES];
// ok, now do UI stuff in the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
[self setCategoryImageName:bigThumb];
[self.imgCategory setImage:pickedImage];
if (status1) {
isAddingCategoryImage = YES;
}
[appDelegate stopLandscapeLoading];
if (error != nil) {
NSLog(#"Error: %#", error);
return;
}
if ([self.popoverController isPopoverVisible]) {
[self.popoverController dismissPopoverAnimated:true];
}
[picker.view removeFromSuperview];
}];
}];
}
thanks and regards,
ALOK
If you use non arc and You had allocated the many object and you did not release these object so it shows the memory problem. you relase all object in dealloc method.In goes upper product option and choose the Analyze. you will see where your application memory leak
If you have used old xcode and you have use new iphone simulator than it shows the memory leak
If you use arc than please comment the autorelease or [obj release] close.
Further than if you want to check their application than side corner button to hold and choose profile. it will show instruments tools. you can enable Nszombies. than you can check how to object values have take and you can see where the memory leak in your application.
No there are is no direct call to free RAM memory in iOS. If you use ARC in your project, define your properties as weak/strong etc correctly and have checked your application for memory leaks or zombie processes there will not be a RAM issue.
iOS frees up memory from other apps to allocate it it to the foreground app if needed and you should not try to deal with it. If you app crashes due to memory issues, you probably have a memory leak in your application. Use Instruments to profile your app.
The memory warning system had a lot of improvements since I started to develop for iOS, ARC also works great helping developers managing memory.
You should profile your app using leaks and allocations to see why your app is consuming so much memory.
Which kind of application are you developing? should be a high memory usage application such as games, or photos app?
A crash could be due to a not well managed answer to a memory warning or to a huge memory occupation that doesn't leave any last breath to your app.
The most common reason are pictures. These devices can't handle a lot of hires resources if you don't manage those situations in the right way, the memory footprint of your app grows until it can't free enough memory.
You need to give more details, though.
I have researched hundreds of posts, but still cannot figure out where my problem is. I have an array of image names and I'm trying to randomly select an image and update the imageview on an event. When run on my device it crashes after 12-15 images.
- (void)viewDidLoad
{
[super viewDidLoad];
self.predictionArray = [[NSArray alloc] initWithObjects:#"IMG_0006.JPG",
#"IMG_0007.JPG",
#"IMG_0008.JPG",
#"IMG_0034.jpg",
#"IMG_0036.jpg",
#"IMG_0043.jpg",
#"IMG_0062.JPG",
#"IMG_0069.JPG",
#"IMG_0076.jpg",
#"IMG_0093.jpg",
#"IMG_0096.jpg",
#"IMG_0168.jpg",
#"IMG_0240.jpg",
#"IMG_0251.jpg",
#"IMG_0262.jpg",
#"IMG_0264.jpg",
#"IMG_0310.jpg",
#"IMG_0351.jpg",
#"IMG_0355.jpg",
#"IMG_0391.jpg",
#"IMG_0404.jpg",
#"IMG_0417.jpg",
#"IMG_0428.jpg",
#"IMG_0461.jpg",
#"IMG_0471.jpg",
#"IMG_0485.jpg",
#"IMG_0492.jpg",
#"IMG_0550.jpg",
#"IMG_0568.jpg",
#"IMG_0822.jpg", nil];
[self makePrediction];
}
- (void) makePrediction {
NSUInteger index = arc4random_uniform(self.predictionArray.count);
[self.pageImage setImage:[UIImage imageNamed:[self.predictionArray objectAtIndex:index ]]];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self makePrediction];
}
You are using the imageNamed: method to set the image of the imageview.But problem is imageNamed: method who has its own caching mechanism that you dont have any control over it.So when & where the allocated memory is released we don't know.So instead of using imageNamed: method use the following method to set the image.
NSString* imgpath= [ [ NSBundle mainBundle] pathForResource:#"sample" ofType:#"png"];
imgviw.image = [ UIImage imageWithContentsOfFile: imgPath];
The main advantage of using the imageWithContentsOfFile: method is this method does NOT cache the image, and therefore does not cause any memory issues with retaining large images. Also before applying image to imageview you can set the imgviw.image = nil & then set the image to avoid the memory leaks problems
I'm using SDWebImage for image caching at UICollectionView in my iOS app.
Everything is fine, however, when user scrolls fast over the collection view there is always little pause before placeholder is replaced with the cached image. I believe this is due to cache checks. For better user experience I would like cells to show the proper image instead of placeholder once an image is actually cached. This would be easy if I could get local (device) filesystem's path of the cached image and store it at the Show instance, and use it (if exists) instead of my placeholder.
There is the possibility to pass success block to the setImageWithURL method However, the only argument it gets is UIImage instance (actually in master branch there is also BOOL variable called cached being passed too). So - is there a possibility to get image's filesystem path straight from the UIImage instance? Or should I modify SDWebImage so it pass that information along to cached instance? Or is there any better way achieve a goal I described earlier?
My Show class has imageURL and here is how show cell use SDWebImage:
#import "ShowCell.h"
#import <SDWebImage/UIImageView+WebCache.h>
#implementation ShowCell
#synthesize imageView = _imageView;
#synthesize label = _label;
#synthesize show = _show;
- (void)setShow:(Show *)show
{
if (_show != show) {
self.label.text = show.title;
if (show.imageURL) {
[self.imageView setImageWithURL:[NSURL URLWithString:show.imageURL]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
}
_show = show;
}
}
#end
There are private SDImageCache methods to get filesystem path to cached image. We can make these methods public with a category. Just put the code below into the, say, SDImageCache+Private.h and add it to your project:
#import "SDImageCache.h"
#interface SDImageCache (PrivateMethods)
- (NSString *)defaultCachePathForKey:(NSString *)key;
- (NSString *)cachedFileNameForKey:(NSString *)key;
#end
I had a similar problem, see: SDWebImage showing placeholder for images in cache
Basically I forked the project to resolve this.
Update:
You can just do this to avoid the flickering (this works with the master branch of the project):
[imageView setImageWithURL:url placeholderImage:imageView.image options:0
progress:^(NSUInteger receivedSize, long long expectedSize) {
imageView.image = [UIImage imageNamed:#"QuestionMarkFace.png"];
} completed:nil];
My text fields and my images picked from image picker all reset set blank if my app stops running, or device is turned off, How can I retain this information?
I've used a singleton (with help from a fellow member) and I can retain my image...that is until the app is killed or device is turned off. Then it's gone.
.m
- (void)viewDidLoad
{
singletonObj = [Singleton sharedSingletonController];
imageView.image = singletonObj.imagePicked;
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[self setImageView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#pragma mark - Action
- (IBAction)done:(id)sender
{
[self.delegate flipsideViewControllerDidFinish:self];
}
- (IBAction)btn:(id)sender {
UIImagePickerController * picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
if((UIButton *) sender == choosePhotoBtn) {
picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
[self presentModalViewController:picker animated:YES];
}
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
NSData *dataImage = UIImageJPEGRepresentation([info objectForKey:#"UIImagePickerControllerOriginalImage"],1);
UIImage *img = [[UIImage alloc] initWithData:dataImage];
singletonObj.imagePicked = img;
imageView.image = img;
[picker dismissModalViewControllerAnimated:YES];
}
#end
There are two types of memory: volatile (RAM) and permanent memory (ie: hard drives and other storage).
Volatile memory is cleared and lost when a program/computer shuts down.
Using a singleton is fine but it's completely unrelated to keeping data from session to session (and by session I mean the time when the program is running: from launch to termination of an application).
You need to store data you wish to keep from session to session to file using any method you want. Depending on the information you want to store, there are different dedicated mechanism for saving:
(such as NSUserDefaults for user preferences).
Core Data is a framework which defines mechanism for structuring data and saving/reading it to file (a.k.a. persistent store).
You can also use serialization.
Or you can always manually manipulate files.
NSData has writeToFile:atomically: which will write create a file out of a data object. If you want to save an image, you must obtain an UIImage's underlying data (i.e.: UIImagePNGRepresentation(...)).
You are going to have to use core data. It can accept NSData from a UIIimage as well as NSStrings. The function within the appDelegate, AppicationWillTerminate, will have to use so that the information is stored just before the application is terminated.
This is going to require some decent amount of work to get it working properly, but nothing too difficult. If you need help understanding core data, I recommend this link
http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started
I am new to iPhone programming and Objective-C.
I am building a View-based application.
The problem is that none of the UIViewController's dealloc functions are ever called.
I have decided to unload all my retained objects programmaticaly, right before presenting the next UIViewController class.
I have resolved all the leaks detected by Xcode, tested the application with Leaks and Allocations Tools and everything seams OK, but the memory used builds up to around 180 MB and the app crashes.
The self.retainCount before presenting the next UIViewController is 1, the objects owned are released and the pointers are nil, but still the memory builds up.
Can you give me a clue? If needed I will post some code samples. Or can you send me some references to something like Objective-C memory management 101?
This is my interface:
#interface GameScreen : UIViewController
{
IBOutlet UILabel *timeLabel;
IBOutlet UIView *gameView;
IBOutlet UIImageView *gameViewFrame;
IBOutlet UIButton *showOutlineButton;
UIImage *puzzleImage;
UIView *optionsView;
UIView *blockView;
NSTimer *timer;
UIImageView *viewOriginalPicture;
SHKActivityIndicator *activityIndicator;
BOOL originalPictureShown;
BOOL outLineShown;
}
#property (nonatomic, retain) IBOutlet UILabel *timeLabel;
#property (nonatomic, retain) IBOutlet UIView *gameView;
#property (nonatomic, retain) IBOutlet UIImageView *gameViewFrame;
#property (nonatomic, retain) IBOutlet UIButton *showOutlineButton;
#property (nonatomic, retain) UIImage *puzzleImage;
And here is the implementation:
- (id) initWithPuzzleImage: (UIImage *) img
{
if((self = [super init]))
{
puzzleImage = [[UIImage alloc] init];
puzzleImage = img;
}
return self;
}
This is the function called when the user taps the exit button:
- (void) onExit
{
[timer invalidate];
CurrentMinuts = 0;
CurrentSeconds = 0;
//remove piece configurations
[pieceConfigMatrix removeAllObjects];
[pieceFramesMatrix removeAllObjects];
PuzzleViewController *modalView = [[PuzzleViewController alloc] init];
[self unloadObjects];
[self presentModalViewController:modalView animated:YES];
[modalView release];
}
And the unloadObjects function:
- (void) unloadObjects
{
[self resignFirstResponder];
[viewOriginalPicture release];
viewOriginalPicture = nil;
[timeLabel release];
timeLabel = nil;
[gameView release];
gameView = nil;
[originalImage release];
originalImage = nil;
[gameViewFrame release];
gameViewFrame = nil;
[timer release];
[showOutlineButton release];
showOutlineButton = nil;
}
I have a lead of what I do wrong, but I am not sure. Let me explain. I am adding the puzzle pieces to the 'gameView' property. For this, I have a 'SplitImage' object. The following function is called in - (void) viewDidLoad:
- (void) generatePuzzleWithImage:(UIImage *) image
{
SplitImage *splitSystem = [[SplitImage alloc] initWithImage:image andPuzzleSize:gPuzzleSize];
[splitSystem splitImageAndAddToView:self.gameView];
[splitSystem release];
}
Next, initialization function for the SplitImage class and the splitImageAndAddToView function:
- (id) initWithImage: (UIImage *) image andPuzzleSize: (int) pSize
{
if((self = [super init]))
{
UIImage *aux = [[[UIImage alloc] init] autorelease];
pieceCenterSize = [SplitImage puzzlePieceSizeForNumberOfPieces:pSize];
UIImage *outSideBallSample = [UIImage imageNamed:#"sampleHorizontal.jpg"]; //convexity size for puzzle size
outSideBallSample = [outSideBallSample resizedImageWithContentMode:UIViewContentModeScaleAspectFit bounds:CGSizeMake(pieceCenterSize, pieceCenterSize) interpolationQuality:kCGInterpolationHigh];
outSideBallSize = roundf(outSideBallSample.size.height);
puzzleSize = pieceCenterSize * pSize;
pieceNumber = pSize;
if(image.size.height < puzzleSize || image.size.height > puzzleSize || image.size.width < puzzleSize || image.size.width > puzzleSize)
{
aux = [SplitImage resizeImageForPuzzle:image withSize:puzzleSize];
aux = [SplitImage cropImageForPuzzle:aux withSize:puzzleSize];
}
aux = [aux imageWithAlpha];
originalImage = [[UIImage imageWithCGImage:aux.CGImage] retain];
mainImage = aux.CGImage;
imageSize = CGSizeMake(aux.size.width, aux.size.height);
NSLog(#"%#", NSStringFromCGSize(imageSize));
splitImageSize = CGSizeMake(pieceCenterSize + 2*outSideBallSize, pieceCenterSize+2*outSideBallSize);
}
return self;
}
- (void) splitImageAndAddToView: (UIView *) view
{
for (int i = 0; i < pieceNumber; i++)
for(int j = 0; j < pieceNumber; j++)
//some code
UIImage *mask;
mask = [self randomlyRetriveMaskWithPrefix:1 forPieceAtI:i andJ:j];
CGImageRef split = CGImageCreateWithImageInRect(mainImage, cuttingRect);
PuzzlePiece *splitView = [[PuzzlePiece alloc] initWithImage:[UIImage imageWithCGImage:split] andMask:mask centerSize:pieceCenterSize objectMatrixPosition:i*pieceNumber+j outSideBallSize:outSideBallSize pieceType:pieceType pieceFrame:cuttingRect];
[pieceFramesMatrix addObject:[NSValue valueWithCGRect:cuttingRect]];
[splitView setTag:(i+1)*100+j];
[view addSubview:splitView];
CGImageRelease(split);
[splitView release];
}
Thank you,
Andrei
Objective-C memory management 101 is here:
https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
Ignore the stuff on garbage collection, it isn't available for iOS.
It's not normal to release objects before presenting the next UIViewController.
Assuming you have a NIB, its contents will be loaded the first time that you (or the system) accesses the view member. Your view controller will get a call to loadView, then subsequently to viewDidLoad. If you have any views you want to add programmatically, you can do that in loadView or viewDidLoad, if you want to interrogate objects loaded from the NIB then you can do that in viewDidLoad.
Once loaded, the view will remain in memory unless and until a low memory warning occurs. At that point it'll be released. You'll get viewDidUnload. You should release anything view related that you've still got an owning reference to in there, and set to nil any weak references you may have.
Any attempt to access the view property subsequently will cause the NIB to be reloaded, etc.
So, when presenting a new UIViewController, just present it. If you create objects that are used only for display of that controller then do so on viewDidLoad, and release them on viewDidUnload.
That all being said, the Leaks tool in Instruments should be able to tell you which types of object are leaking and where you first allocated them, so it makes finding leaks really quite easy. The only thing to watch out for is that if one object handles its properties/members entirely correctly but is itself leaked then anything it creates will generally also leak. So when something leaks, check the thing that created it isn't also leaking before tearing your hair out over why you can't find a problem.
First, retainCount is useless.
Activity Monitor is pretty close to just as useless. As well, the behavior in the simulator can be quite different on the memory use front. Better to focus debugging of a problem like this on the device.
Next, this:
The problem is that none of the
UIViewController's dealloc functions
are ever called. I have decided to
unload all my retained objects
programmaticaly, right before
presenting the next UIViewController
class.
Any time you find yourself programmatically working around incorrect behavior, you are just creating more bugs.
Step back from your custom hack and figure out why the dealloc isn't being called. Something somewhere is over-retaining the object. The allocations instrument with retain tracking turned on will show you exactly where all retains and releases are sent to the errant objects.
Leaks likely won't show anything if whatever is retaining the objects is still reachable from a global or the stack (i.e. leaks are objects that can never be used by your program again -- but there are many more ways to explode memory use without it being truly a leak).
You should also "Build and Analyze", then fix any problems it identifies.
Next, if you are seeing memory accretion of repeated operations on the user's part, then Heapshot analysis is extremely effective at figure out exactly what has gone wrong.
Some specific comments:
puzzleImage = [[UIImage alloc] init];
puzzleImage = img;
2 bugs; you are leaking a UIImage and not retaining img. That your app doesn't crash in light of the above code indicates that there is likely an over-retain elsewhere.
retainCount is not a reliable debugging tool.
You should never pay attention to or rely on retainCount. Just because you released it does not mean that some part of the program does not still have a reference to it. retainCount has no value.
Generally as a rule of thumb. If you use 'alloc' you must 'release' at some point.
Unless ofcourse you have put it into an autorelease pool.
Leaks should be able to point you to the objects that are leaking, using that, narrow down to where those objects are added, stored etc.
Post examples of your code on how you instantiate objects and where you release them, you maybe doing something wrong early on.
edit: apologies, i put 'init' not 'alloc' previously, thank you dreamlax, early morning mistake.