I have noticed that the memory usage of a program I wrote, keep increasing over time. XCode's instruments shows no memory leak, yet you can see the heap stack growing over time..
Upon investigation, a lot of the memory usage come from the IBOutlet UI objects. The interface is build with Interface Builder.
Typical usage would be like:
header:
#interface HelpViewController : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *webView;
IBOutlet UIBarItem *backButton;
IBOutlet UIBarItem *forwardButton;
NSString *URL;
IBOutlet UIActivityIndicatorView *spin;
}
#property (nonatomic, retain) NSString *URL;
And for the usage :
- (void)webViewDidStartLoad:(UIWebView *)mwebView {
backButton.enabled = (webView.canGoBack);
forwardButton.enabled = (webView.canGoForward);
[spin startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
backButton.enabled = (webView.canGoBack);
forwardButton.enabled = (webView.canGoForward);
[spin stopAnimating];
}
Looking at the heap stack, you find that the UIActivityIndicatorView *spin object isn't properly deallocated, and its memory footprint will keep growing.
However, if I change the code to:
header:
#interface HelpViewController : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *webView;
IBOutlet UIBarItem *backButton;
IBOutlet UIBarItem *forwardButton;
NSString *URL;
UIActivityIndicatorView *spin;
}
#property (nonatomic, retain) NSString *URL;
#property (nonatomic, assign) IBOutlet UIActivityIndicatorView *spin;
And in the code I do:
synthesize spin;
- (void)webViewDidStartLoad:(UIWebView *)mwebView {
backButton.enabled = (webView.canGoBack);
forwardButton.enabled = (webView.canGoForward);
[self.spin startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
backButton.enabled = (webView.canGoBack);
forwardButton.enabled = (webView.canGoForward);
[self.spin stopAnimating];
}
Nothing more, nothing else, then the heap stack doesn't grow anywhere as much.. and the UIActivityIndicatorView object doesn't leave any stuff behind
I can't figure out why it would make a difference here having an assign property or not, it just doesn't make sense ! Unless I massively misunderstood what's happening.
Any explanations would be welcomed..
Thanks
You need to release the objects in the dealloc method:
-(void)dealloc {
[webView release];
[backButton release];
[forwardButton release];
[URL release];
[spin release];
[super dealloc];
}
The reason why the problem doesn't occour in your second version is, that you set the property with the attribute assign, usually you should use retain for "object-properties" assign is usually used for the basic datatypes like int, float, bool etc...
EDIT:
to the part with retain and assign, afaik the behaviour is the following:
If the property is made with assign, then setting
self.thatVariable = something;
would be the same as:
thatVariable = something;
if you used retain it would be the same as:
[thatVariable release];
thatVariable = [something retain];
So if you used assign for variables that hold pointers to objects you can't be sure, that your object isnt deallocated somewhere else, which would result in a bad access.
Afaik the only reason to use assign with object is to get a weak reference. If you have to object which would both retain each other, none of it would ever get released. so thats a spot where you would use assign for objects. (f.e. often in the delegate-pattern. the object would retain it's delegate and the delegate would retain the object. In this case the delegate is often assigned)
Related
I just recently started learning Objective C/Cocoa and I know how important the memory management is and I believe this error I've been having is regarding to that.
I have a very very simple screen: two UITextView, one Button, one UILabel.
My header file has:
#interface PontaiViewController : UIViewController {
UITextField *loginField;
UITextField *passwordField;
UILabel *userID;
}
#property (nonatomic, retain) IBOutlet UITextField *loginField;
#property (nonatomic, retain) IBOutlet UITextField *passwordField;
#property (nonatomic, retain) IBOutlet UILabel *userID;
- (IBAction) btnLoginClicked:(id) sender;
The implementation has:
#implementation PontaiViewController
#synthesize loginField;
#synthesize passwordField;
#synthesize userID;
-(IBAction) btnLoginClicked:(id)sender {
NSString *string1 = #"username=";
NSString *string2 = [string1 stringByAppendingString:(loginField.text)];
NSString *string3 = [string2 stringByAppendingString:(#"&password=")];
NSString *post = [string3 stringByAppendingString:(passwordField.text)];
NSLog(#"The post is %#", post);
userID.text=loginField.text;
[string1 release];
[string2 release];
[string3 release];
[post release];
}
and it finishes with
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.loginField=nil;
self.passwordField=nil;
self.userID=nil;
}
- (void) dealloc {
[super dealloc];
[loginField release];
[passwordField release];
[userID release];
}
When I run this demo, and try to write something in the TextView, I get this error.
What could it be?
Regards,
Felipe
Also, your NSStrings are autoreleased, and then you're releasing them again (over releasing). Read up on memory management of convenience methods.
stringByAppendingString returns an autoreleased object, don't release string1, string2, string3 and post.
In viewDidUnload you set loginField to nil, then you try to release it in dealloc. This isn't right. You only need to release valid items that you own.
Additionally, (as pointed out in a comment) you need to put [super dealloc] at the end of the dealloc function.
As pointed out by others, you also should not release the strings you're getting from stringByAppendingString.
Here are some basic rules about how to manage memory in Objective-C under iOS:
https://developer.apple.com/library/ios/#documentation/general/conceptual/devpedia-cocoacore/MemoryManagement.html
One thing you will find is that you only release stuff you are responsible for, and you're not responsible for it unless it was created with one of these:
alloc, allocWithZone:, copy, copyWithZone:, mutableCopy, mutableCopyWithZone
You should comment out the following
//[string1 release];
//[string2 release];
//[string3 release];
//[post release];
since you are using helper methods and not explicitly allocating anything.
I'm trying to change the text of a UILabel with text from an array upon a button click, but it doesn't do anything.
#interface Test01AppDelegate : NSObject <UIApplicationDelegate> {
UILabel *helloLabel;
UIButton *hellobutton;
NSMutableArray *madWords;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UIButton *hellowButton;
#property (nonatomic, retain) IBOutlet UILabel *hellowLabel;
#property (nonatomic, retain) NSMutableArray *madWords;
- (void) madArrays;
- (IBAction)helloYall;
#end
and
#import "Test01AppDelegate.h"
#implementation Test01AppDelegate
#synthesize window = _window;
#synthesize hellowButton;
#synthesize hellowLabel;
#synthesize madWords;
- (void) madArrays {
[madWords addObject:#"Part A"];
[madWords addObject:#"Part B"];
[madWords addObject:#"Part C"];
[madWords addObject:#"Part D"];
}
- (IBAction)helloYall {
[self madArrays];
self.hellowLabel.text = [madWords objectAtIndex:0];
}
I can set the helloLabel text with
#"some text here";
and it works fine. Also, I tried copying the "madArrays" method into the "helloYall" method and it still didn't work. As I said, I can manually set the text and it works, but I'd like to pull the info from an array. Eventually, I'd like to loop through the array to grab the text on each button press, but one step at a time. Thanks.
You never create the madWords array. You need to add:
self.madWords = [NSMutableArray array];
at the top of:
- (void) madArrays {
would probably be a good place. Other possibly good places would be i the class init method or the view controller viewWillAppear method.
// Or you can try this in your init Method:
//first allocate the ivar
- (void)myInitMethod {
madArrays = [[NSMutableArray]alloc]init];
}
//then you can do anything directly to the ivar or throughout de setter
- (void)doAnythingWithiVar {
// do your stuff
}
//when you are done you can dealloc your ivar
- (void)dealloc {
[madArrays release];
[super dealloc];
}
It looks like madArrays is still nil when you come to populate it. At some point you need something like [self setMadArrays:[[NSMutableArray alloc] init]];. Also, don't forget to release madArrays in the dealloc before calling super as you'll have a memory leak.
I can't work out how to set the previewItemTitle property for my QLPreviewController class. Its seems a bit strange as the iPhone developer document for this class says that that property is #property (readonly) which would mean that I cannot set it.
Any ideas. Thanks
My code:
QLPreviewController *preview = [[QLPreviewController alloc] init];
[preview setDataSource:self];
[self presentModalViewController:preview animated:YES];
QLPreviewController has no previewItemTitle property. You mean the QLPreviewItem protocol.
"Readonly" means that you can't set it via the property (unless it's overridden); i.e. the property does not declare a setPreviewItemTitle: method. This makes sense for the protocol: the controller does not expect to be able to set the preview item titles.
For the most basic preview item, you could use something like this:
#interface BasicPreviewItem : NSObject<QLPreviewItem>
{
}
#property (nonatomic, retain) NSURL * previewItemURL;
#property (nonatomic, copy) NSString* previewItemTitle;
#end
#implementation BasicPreviewItem
#synthesize previewItemURL, previewItemTitle;
-(void)dealloc
{
self.previewItemURL = nil;
self.previewItemTitle = nil;
[super dealloc];
}
#end
However, the point of the protocol is so that you can take any class and add -(NSURL*)previewItemURL and -(NSString*)previewItemTitle methods (e.g. if you had a music player, you could add those methods to the "Track" class and be able to preview tracks).
So, I'm loading by XIB file and it contains a set of UIBarButtonItems. Some of the items are used when the viewDidLoad: is called.
#interface MyViewController : UIViewController {
IBOutlet UIBarButtonItem *addButton;
IBOutlet UIBarButtonItem *editButton;
IBOutlet UIBarButtonItem *doneButton;
}
// NB: There are no properties retaining anything.
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *initialToolbarItems =
[[NSArray alloc] initWithObjects: addButton, editButton, nil];
self.toolbarItems = initialToolbarItems;
[initialToolbarItems release];
}
- (void)dealloc {
[super dealloc];
// Nothing else to do here since we are not retaining anything.
// … or are we? <insert dramatic music here>
}
#end
If I push the above the above ViewController onto a UINavigationController everything seems fine, all the IBOutlets are assigned and behave like expected.
The instant i pop the ViewController from the navigation stack Instruments' Leaks tells me that I am leaking a UIBarButtonItem. Woe is me!
If I change dealloc: to
- (void)dealloc {
[doneButton release];
[super dealloc];
}
no leaks occur. The same goes if I use doneButton in viewDidLoad:
NSArray *initialToolbarItems =
[[NSArray alloc] initWithObjects: addButton, editButton, doneButton, nil];
My question: Why is my IBOutlet leaking when I don't use it. I don't retain it at any point. The the NIB loader should own the object, right?
Only thing I can think of:
The nib loader treats IBOutlets as strong references. All outlets are retained by default unless you specifically indicate assign. So you still need to release them in dealloc and viewDidUnload.
You can also use a assigned property to make it a weak reference:
#property (nonatomic, assign) IBOutlet UIBarButtonItem *doneButton;
Some reading: http://weblog.bignerdranch.com/?p=95
If you have #property with (retain) declared for the your IBOOutlets they will be retained and must be released
The array retains them
Do you have to release IBOulets in dealloc? I'm not sure on this one because I didn't do the alloc and typically you only release for something you called alloc on. Anyone know?
Your IBOutlets are probably #properties. If they are, and you have retain as an attribute, then you do need to release in -dealloc
In other words:
#interface MyViewController : UIViewController {
IBOutlet UITableView *myTable;
}
#property (nonatomic, retain) IBOutlet UITableView *myTable;
#end
You will have to [myTable release]; in your dealloc.
If you make a new Navigation Based App in Xcode, and look in the appdelegate.h:
#interface Untitled1AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
and the dealloc for appdelegate.m:
- (void)dealloc {
[navigationController release];
[window release];
[super dealloc];
}
The key thing to see here are lines like these:
#property (nonatomic, retain) IBOutlet UIWindow *window;
If there is a retain in there, that means that the property is being "owned" by your code and you have to release it.
Of course, there are other ways, such as not declaring the IBOutlets as properties, or declaring them as properties without retaining. I find that in most cases I prefer to have them be retained properties, which I then have to explicitly release. An example of this is when you flip from one view controller to another. As one view controller is dismissed, its views are removed and they are released. Any IBOutlet UILabels on that view would get released too if I don't have them retained. That means that when I flip back to the old view, I have to go through and reset my labels and controls to their last values, when I could have easily kept them saved if I just retain the IBOutlet.
If you just use IBOutlet in your interface, you DO NOT need to release them. Reason being is that unless you explicitly retain them in your code, they are merely being set. They stick around because the view is there. Obviously, if you also use properties and retain them, you need to release on dealloc.
It's not about IBOutlet, it's about your declaration.
If you use a new project wizard in Xcode, you probably get some code like this in your header file.
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
You can see, there's retain keyword in the header file. Following the memory management guideline, you MUST release everything your retain (by calling alloc, copy, retain, etc.). And you have retain in your code then you must release it.
Additionally, the wizard already add some release code for you.
- (void)dealloc {
[tabBarController release];
[window release];
[super dealloc];
}
As you said, you should release everything that you allocated yourself (with alloc or copy). It works the other way: you should not release any Cocoa objects you did not allocate yourself (some CoreFoundation functions allocate, and you're responsible for releasing them, but it's not the case here).
If you didn't allocate your IBOutlet, then you don't have to release it, unless of course, for some reason, you retained it somewhere.
To answer the side question by Joe D'Andrea. You can use self.label = nil;. Because it is calling setLabel, which is auto generated:
- (void)setLabel:(UILabel *)input
{
[label autorelease];
label = [input retain];
}
As you can see the current label will be released then nil is assigned to label.
But make sure you don't write it as label = nil. That will not work.
Because you need to call the auto generated label accessor method.
Here's what I have been doing with regard to IBOutlet objects (in conjunction with a NIB file):
#interface MyViewController : UIViewController {
UILabel *label;
}
#property (nonatomic, retain) IBOutlet UILabel *label;
#end
#implementation MyViewController
#synthesize label;
- (void)setView:(UIView *)aView {
if (!aView) {
// view is being set to nil
// set outlets to nil to get the benefit of the didReceiveMemoryWarning!
self.label = nil;
}
// Invoke super's implementation last
[super setView:aView];
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.label = nil;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)dealloc {
[label release];
[super dealloc];
}
Side question: Does it make more sense to use self.label = nil in dealloc, or must release be explicitly called (for instance, to keep the static analyzer happy)?
I suppose, at that point, we're on our way out anyway, so there's no need to set our IBOutlet objects to nil.
If this is your BlahViewController.h:
// BlahViewController.h
#import <UIKit/UIKit.h>
#interface BlahViewController
{
IBOutlet UINavigationBar *navigationBar;
}
#property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar;
#end
Then this would be your dealloc in BlahViewController.m:
- (void)dealloc
{
[navigationBar release];
[super dealloc];
}
However, if this is your BlahViewController.h:
// BlahViewController.h
#import <UIKit/UIKit.h>
#interface BlahViewController
{
IBOutlet UINavigationBar *navigationBar;
}
#end
Then this would be your dealloc in BlahViewController.m:
- (void)dealloc
{
[super dealloc];
}
And finally, if this is your BlahViewController.h:
// BlahViewController.h
#import <UIKit/UIKit.h>
#interface BlahViewController
{
IBOutlet UINavigationBar *navigationBar;
IBOutlet MKMapView *map;
}
#property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar;
#end
Then this would be your dealloc in BlahViewController.m:
- (void)dealloc
{
[navigationBar release];
[super dealloc];
}
In short, if you declare it as a property, with retain, then you need to release it.