objective-c memory management: caching view elements - iphone

I'm writing an application which is quite graphically heavy and therefore I'm trying to implement a caching mechanism within my view controller that creates a view once, and retains it for future use, similar to the following:
- (UIView *)logoView
{
if(_logoView == nil)
{
_logoView = [[UIImageView alloc] initWithImage: [UIImage imageNamed: #"logo_header.png"]];
_logoView.contentMode = UIViewContentModeCenter;
}
return _logoView;
}
This all works fantastically, however, I'm getting a bit confused over a certain issue; when I simulate a memory warning I call a method which purges the cache, like so:
- (void)purgeCachedContent
{
if(_logoView != nil)[_logoView release];
}
However, this seems to be causing my application issues, as it can get called several times (dealloc, didReceiveMemoryWarning and viewDidUnload).
How can I determine if an instance exists to send it a release message? Even when I try to NSLog a released view I receive a EXC_BAD_ACCESS error, so I'm having trouble figuring out the best way I'd do it. I even removed the if statement hoping to rely on being able to send nil objects messages, but this causes the same error...
I'm sure it's something simple missing from my objective-c knowledge... I just don't know what!

_logoView isn't set to nil automatically just by releasing it, so any future methods you try to call using that pointer will go to a memory location that used to contain a valid object, but now contains junk. You can fix this by setting _logoView = nil; after releasing it.

If you want to cache it why would you want to release it?
just use autorelease on init:
_logoView = [[[UIImageView alloc] initWithImage: [UIImage imageNamed: #"logo_header.png"]] autorelease];

Related

Accessors / Getters and Lazy Initialization

I have a question about overriding auto-generated accessor methods. The following would not work (I believe) because each getter references the other getter. Is there a rule that accessor methods should not use other accessor methods, or do you just have to watch out for these situations individually?
-(UIImage *) image{
if(image == nil){
if(self.data == nil){
[self performSelectorInBackground: #selector(loadImage) withObject: nil]
}else{
self.image = [UIImage imageWithData: self.data];
}
}
return image;
}
-(NSData *) data {
if(data == nil){
if(self.image == nil){
[self performSelectorInBackground: #selector(loadData) withObject: nil]
}else{
self.data = UIImageJPEGRepresentation(self.image, 0.85);
}
}
return data;
}
I have to emphasize that the image use presented here is an example, and thoughts concerning what to do in this particular example are less important than in the general case.
First, don’t be too smart for your own good. If you want to get over some bottleneck, first measure and make sure it’s really there. I believe that both UIImage and NSData do some internal lazy loading, so that your code might be essentially useless. Second, even if you really wanted to do something like that by hand, try splitting the caching code into a separate class, so that you don’t pollute the code of the main class.
There is no rule about accessors (at least no that I know of), because people don’t do much lazy loading in accessors. Sometimes I get myself caught by an endless loop caused by lazy [UIViewController loadView] in combination with [UIViewController view], but that’s about it.
There's nothing that forbids it, but you're definitely writing some confusing code. Essentially, these two properties have a circular dependency. This is hard to both read and debug. It's not clear why you'd want to "load the data" before you "load the image", or why you'd also want to support "loading the image" before you "load the data", or really how the two properties are really two different things.
what you're actually doing in this example could take a long time to load; it is best to ensure that it is thread safe.
furthermore, it would be even better if you made the data object a real data provider (typically using protocols), separate from the class, and to make the image container expose that it is handling a lazily loaded object (which may cause outcry from some people).
specifically, you may want to invoke a load data/image from the provider, call it from awakeFromNib (for example) - then the loader runs off and loads the data on a secondary thread (esp. if downloaded). give the data provider a callback to inform the view that the image is ready (typically using protocols). once the view takes the unarchived image, invalidate the data provider.
finally, if you're dealing with app resources, the 'system' will cache some of this for you sooo you'd be attempting to work against what's already optimised behind the scenes.
in short, it's usually ok (e.g. not lazy initialization) - but this specific design (as another poster said) has a circular dependency which should be minimized.

NSZombieEnabled breaking working code?

I have the following method in UIImageManipulation.m:
+(UIImage *)scaleImage:(UIImage *)source toSize:(CGSize)size
{
UIImage *scaledImage = nil;
if (source != nil)
{
UIGraphicsBeginImageContext(size);
[source drawInRect:CGRectMake(0, 0, size.width, size.height)];
scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return scaledImage;
}
I am calling it in a different view with:
imageFromFile = [UIImageManipulator scaleImage:imageFromFile toSize:imageView.frame.size];
(imageView is a UIImageView allocated earlier)
This is working great in my code. I resizes the image perfectly, and throws zero errors. I also don't have anything pop up under build -> analyze. But the second I turn on NSZombieEnabled to debug a different EXC_BAD_ACCESS issue, the code breaks. Every single time. I can turn NSZombieEnabled off, code runs great. I turn it on, and boom. Broken. I comment out the call, and it works again. Every single time, it gives me an error in the console: -[UIImage release]: message sent to deallocated instance 0x3b1d600. This error doesn't appear if `NSZombieEnabled is turned off.
Any ideas?
--EDIT--
Ok, This is killing me. I have stuck breakpoints everywhere I can, and I still cannot get a hold of this thing. Here is the full code when I call the scaleImage method:
-(void)setupImageButton
{
UIImage *imageFromFile;
if (object.imageAttribute == nil) {
imageFromFile = [UIImage imageNamed:#"no-image.png"];
} else {
imageFromFile = object.imageAttribute;
}
UIImage *scaledImage = [UIImageManipulator scaleImage:imageFromFile toSize:imageButton.frame.size];
UIImage *roundedImage = [UIImageManipulator makeRoundCornerImage:scaledImage :10 :10 withBorder:YES];
[imageButton setBackgroundImage:roundedImage forState:UIControlStateNormal];
}
The other UIImageManipulator method (makeRoundCornerImage) shouldn't be causing the error, but just in case I'm overlooking something, I threw the entire file up on github here.
It's something about this method though. Has to be. If I comment it out, it works great. If I leave it in, Error. But it doesn't throw errors with NSZombieEnabled turned off ever.
The purpose of NSZombieEnabled is to detect messages that get sent to objects after they've been deallocated. The console error you're seeing is NSZombieEnabled telling you that a release message is being sent to a deallocated instance of UIImage. Usually a bug like this is the result of too many calls to release, or not enough calls to retain.
In this case, your scaleImage:toSize: method returns an autoreleased UIImage. The error message you're getting from NSZombieEnabled suggests that you may be releasing this object after it gets returned. This would explain your bug. When your autorelease pool drains it would try to release an object that's already been deallocated.
You're passing imageFromFile to scaleImage:toSize:, and then reassigning that same variable to the return value. There's nothing wrong with this idiom per se, but does require some extra care to avoid memory bugs like this one. You're overwriting your reference to the original object, so you either have to make sure it's autoreleased before the assignment, or save a separate reference that you can manually release after the assignment. Otherwise your original object will leak.
The error was due to a release going on in the makeRoundedCornerImage method from UIImageManipulator. Still not sure why it wasn't getting picked up without NSZombieEnabled turned on, but that's what it was.
You can find the offending line in the Gist I posted in the original question: Line 74.

Is it better to autorelease or release right after?

There are a lot of cases in which one would alloc an instance, and release it right after it's being assigned to something else, which retains it internally.
For example,
UIView *view = [[UIView alloc] initWithFrame...];
[self addSubView:view];
[view release];
I have heard people suggesting that we go with autorelease rather than release right after.
So the above becomes:
UIView *view = [[[UIView alloc] initWithFrame...] autorelease];
[self addSubView:view];
What's the best practice here? Pros and cons?
In most cases, it wont really matter either way. Since -autorelease simply means that the object will be released at the end of the current iteration of the run loop, the object will get released either way.
The biggest benefit of using -autorelease is that you don't have to worry about the lifetime of the object in the context of your method. So, if you decide later that you want to do something with an object several lines after it was last used, you don't need to worry about moving your call to -release.
The main instance when using -release will make a noticeable difference vs. using -autorelease is if you're creating a lot of temporary objects in your method. For example, consider the following method:
- (void)someMethod {
NSUInteger i = 0;
while (i < 100000) {
id tempObject = [[[SomeClass alloc] init] autorelease];
// Do something with tempObject
i++;
}
}
By the time this method ends, you've got 100,000 objects sitting in the autorelease pool waiting to be released. Depending on the class of tempObject, this may or may not be a major problem on the desktop, but it most certainly would be on the memory-constrained iPhone. Thus, you should really use -release over -autorelease if you're allocating many temporary objects. But, for many/most uses, you wont see any major differences between the two.
I agree with Matt Ball. Let me just add that, if you find yourself using this pattern frequently, it can be handy to write a quick category:
#interface UIView (MyCategories)
- (UIView *)addNewSubviewOfType:(Class)viewType inFrame:(NSRect)frame;
#end
#implementation UIView (MyCategories)
- (UIView *)addNewSubviewOfType:(Class)viewType inFrame:(NSRect)frame
{
UIView * newView = [[viewType alloc] initWithFrame:frame];
[self addSubView:newView];
return [newView autorelease];
}
#end
Which can be used as follows:
UIView * view = [someView addNewSubviewOfType:[UIView class]
inFrame:someFrame];
And it even works with other types, as long as they are derived from UIView:
UIButton * button = [mainView addNewSubviewOfType:[UIButton class]
inFrame:buttonFrame];
I usually go for -release rather than -autorelease whenever possible. This comes from years of experience debugging and enhancing other people's Objective-C code. Code that uses autorelease everywhere makes it harder to debug when an object gets over-released, since the extra release happens far away from the incorrect code.
It's also the case that many folks use autorelease when they just don't understand how cocoa memory management works. Learn the rules, learn the API, and you'll almost never need to autorelease an object.
A last minor point is that if you don't need the autorelease behavior, then using autorelease just needlessly adds extra work for your program to do.

Why am i getting a EXC_BAD_ACCES

Hey. I have been working on a Twitter application and have been stuck on a EXC_ BAD_ ACCESS error for quite some time. I know that EXC_ BAD_ ACCESS is a memory issue but i cannot pinpoint where the problem is. Here is my code sample:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *path = #"/Volumes/Schools/BHS/Student/740827/Documents/Forrest McIntyre CS193P/Presence2";
NSArray *propList = [NSArray arrayWithContentsOfFile:[NSBundle pathForResource:#"TwitterUsers" ofType:#"plist" inDirectory:path]];
people = [[NSMutableArray alloc]init];
for (NSString *name in propList) {
Person *p = [[Person alloc] initWithUserName: name];
[people addObject: p];
[p release];
}
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
The exception is thrown on the last brace after the comment. I believe that it is truly thrown in the for loop somewhere but just shows up upon exiting.
Here is the implementation file for Person:
#implementation Person
#synthesize image;
#synthesize username;
#synthesize displayName;
#synthesize statusArray;
-(id)initWithUserName:(NSString *)userName {
if(self = [super init])
{
self.username = userName;
NSDictionary *info = [TwitterHelper fetchInfoForUsername:userName];
self.displayName = [info objectForKey:#"name"];
NSLog([NSString stringWithFormat:#"%#",[info objectForKey:#"profile_image_url"]]);
NSString *imageURL2 = [NSString stringWithFormat:#"%#",[info objectForKey:#"profile_image_url"]];
self.image = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: imageURL2]]];
[info release];
self.statusArray = [TwitterHelper fetchTimelineForUsername:userName];
}
return self;
}
#end
Thanks for any help
EDIT: Here is the header file for PersonListViewController (the class that contains the ViewDidLoad).
This is just to show you where people is coming from.
#interface PersonListViewController : UITableViewController {
NSMutableArray *people;
}
#end
since you never retain propList or path you shouldn't be releasing them.
You should, however, release people
For an overview of memory management, see the Memory Management Programming Guide
For quick fixes, try the static analyzer.
I think the problem is here:
[propList release];
Since you created propList using arrayWithContentsOfFile you don't need to release it - it will be automatically released. The autorelease is actually what's causing the error since it is trying to release something that you already released manually.
ETA: as cobbal mentioned, you also don't need to release path.
Debugging EXC_BAD_ACCESS is difficult to debug. This happens when a message is sent to an object that is already released. You need to find out what is causing this generic error by turning on NSZombiEnabled environment variable so the Objective-C environment will be able to 'track' a deallocated object. Using this, when you get the error you can determine where the error occurred by looking at the call stack. You won't know where it is released but at least it will get you close.
I don't have it setup here, but you may also be passing a pointer to the error which will cause the object to not persist as a zombie/dummy.
Bottom line, you need to make sure the variables you are meaning to release, that you retain them as necessary.
This Technical Q&A by Apple gives tips on Finding bugs with EXC_BAD_ACCESS.
For one, neither of these are necessary in your example:
[path release];
[propList release];
because:
path is a string literal (will always exist)
propList is autoreleased
For any EXC_BAD_ACCESS errors, you are usually trying to send a message to a released object. The BEST way to track these down is use NSZombieEnabled.
This works by never actually releasing an object, but by wrapping it up as a "zombie" and setting a flag inside it that says it normally would have been released. This way, if you try to access it again, it still know what it was before you made the error, and with this little bit of information, you can usually backtrack to see what the issue was.
It especially helps in background threads when the Debugger sometimes craps out on any useful information.
VERY IMPORTANT TO NOTE however, is that you need to 100% make sure this is only in your debug code and not your distribution code. Because nothing is ever released, your app will leak and leak and leak. To remind me to do this, I put this log in my appdelegate:
if(getenv("NSZombieEnabled") || getenv("NSAutoreleaseFreedObjectCheckEnabled"))
NSLog(#"NSZombieEnabled/NSAutoreleaseFreedObjectCheckEnabled enabled!");
If you need help finding the exact line, Do a Build-and-Debug (CMD-Y) instead of a Build-and-Run (CMD-R). When the app crashes, the debugger will show you exactly which line and in combination with NSZombieEnabled, you should be able to find out exactly why.
http://www.cocoadev.com/index.pl?NSZombieEnabled can be useful in tracking down EXC_BAD_ACCESS bugs. Instead of deallocating objects when they are released it puts them into a zombie state that raises an exception when they are subsequently accessed. Just be sure not to ever release code with this flag set, as it will leak memory like a sieve.
what is self.editButtonItem? I don't see it in your .h file
A couple of things.
In initWithUserName: you're getting info from a method that doesn't contain alloc/copy/create. Further, you don't explicitly retain it. Yet you release it. This is problematic assuming fetchInfoForUsername: autoreleases its result as expected according to the Cocoa Memory management rules.
Using property accessors in initializers is considered bad form since it can cause KVO notifications to be sent out for a half-baked instance.

Caption update on button causing IPhone app to crash

I'm currently teaching myself Objective-C and Iphone Development using the very good 'Begining IPhone Development'. I have been playing around with one of the sample applications and I am trying to update one button with text from a text field when another button is pressed. I have set up my Actions and links and all that jazz. The code of the single method/function/call is below
-(IBAction)updateButtonPressed
{
NSString *newCaption = [[NSString alloc] initWithString:#"."];
newCaption = tfUpdateText.text;
[btnPressMe setTitle:newCaption forState:UIControlStateNormal];
[newCaption release];
}
It works perfectly the first time I press the button and maybe for two or three times after then crashes. I'm obviously doing something really stupid but I cant see it. This is all I added (as well as declarations, property - synthesize etc). Can someone point out my obvious memory leak.
Update:
If I change to this
-(IBAction)updateButtonPressed
{
[btnPressMe setTitle:tfUpdateText.text forState:UIControlStateNormal];
}
It works fine but could someone explain to me what mistake I was making?
You are incorrectly managing memory. What is the -initWithString:#"." for? You're generating a constant string #".", then leaking it, then pointing to a different string (tfUpdateText.text), then assigning that pointer to the title, then releasing the -text object.
This is both a leak and an over-release. It's the over-release that's crashing.
Perhaps you meant this:
-(IBAction)updateButtonPressed
{
[btnPressMe setTitle:tfUpdateText.text forState:UIControlStateNormal];
}
You have a memory management bug. The newCaption reference object you are releasing is different from the one you initialized. You are accidentally leaking the NSString you allocated, and releasing tfUpdateText.text instead.
You can remove the temperory variable like:
-(IBAction)updateButtonPressed
{
[btnPressMe setTitle:tfUpdateText.text forState:UIControlStateNormal];
}
You're not using NSString properly here (and are really doing a lot more work than required). NSStrings are just pointers, so your second assignment to newCaption is just orphaning the first. When you then send [newCaption release] later on, you're not sending it to your alloc'd object, but rather to tfUpdateText.text, which you didn't retain. Get rid of the alloc and the release, and you should be all set.