Obj-C, Potential leak of an object allocated on line, UIBarButtonItem alloc - objective-c

I'm getting an analyser leak, however this is the same code i'm using elsewhere without a problem. I know I'm using alloc and therefore I have to release, but I am doing this in dealloc.
What am I doing wrong ?
Header file:
#interface myViewController : UIViewController <UITableViewDataSource,
UITableViewDelegate> {
UIBarButtonItem *addButton;
}
#property (nonatomic, retain) UIBarButtonItem *addButton;
Main file:
#synthesize addButton;
- (void)viewDidLoad {
NSMutableArray* buttons = [[NSMutableArray alloc] initWithCapacity:3];
addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
addButton.style = UIBarButtonItemStyleBordered;
[buttons addObject:addButton];
[tools setItems:buttons animated:NO];
[buttons release];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithCustomView:tools];
addButton.enabled = FALSE;
- (void)dealloc {
[addButton release];

when you use a property and assign to it the attributes you specified determine whether retainCount is incremented if you assign to the property. In your case you specified "retain" which means that the setter function that handles assignment to your property will automatically increment the retain count for the object.
However when you write
addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
you are creating an opject with already retain count == 1 so when you assign it will have retain count 2. the proper way to do this is to create a temp variable and create the object, then assign the temp variable to the property thereafter releasing the temp. variable:
UIBarButtonItem* tmp = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
self.addButton = tmp;
[tmp release];
of course i would recommend a more descriptive name than 'temp' as variable name.

You are not using the setter, the code should be:
self.addButton = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)] autorelease];
This type of problem can be avoided by using an ivar that has a different name than the property name. This is accomplished in the #synthesize statement:
#synthesize addButton = _addButton;
This way any omission of self will result in an error message.
Here is a full implementation (except tools is undefined), the property addButton is handles in all places:
#interface myViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> {
}
#property (nonatomic, retain) UIBarButtonItem *addButton;
#end
#implementation myViewController
#synthesize addButton = _addButton;
- (void)viewDidLoad {
NSMutableArray* buttons = [NSMutableArray array];
self.addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(btnNavAddPressed:)];
self.addButton.style = UIBarButtonItemStyleBordered;
[buttons addObject:self.addButton];
[tools setItems:buttons animated:NO];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:tools] autorelease];
self.addButton.enabled = FALSE;
}
- (void)dealloc {
[_addButton release];
}
#end

Both the above answers are misleading. You don't need to use a setter, it's perfectly fine to assign objects directly to iVars. You do need to release anything you alloc or retain however. The problem you have is here:
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:tools];
This line is alloc'ing a UIBarButtonItem instance and setting it to the rightBarButtonItem property of the navigationItem. That means the navigationItem is retaining the UIBarButtonItem and it is responsible for that retain. You are responsible for releasing it b/c of the alloc and you are not. Change the code to this:
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:tools] autorelease];
and this leak goes away.

You are not taking advantage of the declared property but I don't see any problem with addButton. The leak seems more in:
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithCustomView:tools];
Just add autorelease and the leak will go away.

Related

How do I 'share' pieces of code throughout my whole app?

I am working on a simple app. This app has various textfields that require the 'Done' button in a toolbar.
Currently, this toolbar requires <10 lines of code, but I dislike the fact that for every textfield in every viewController I need to add those lines.
So, is there a way to shorten this 'toolbar setup'? Ideally, I would need to call a single method containing all I need:
[self addDefaultToolbar:field]
But then again adding this method everywhere I need seems a bit 'dirty' (maybe this is not clear enough, self implies that within the controller there's a method called addDefaultToolBar).
So this is what I tried:
I made a nice folder, an header and an .m file. In the header I put this:
#ifndef UIUtils_h
#define UIUtils_h
#import <UIKit/UIKit.h>
#interface UIUtils : NSObject
-(void)addDefaultToolbar:(UITextField *)field;
#end
#endif /* UIUtils_h */
And in the .m file I put this:
#import "UIUtils.h"
#implementation UIUtils
-(void)addDefaultToolbar:(UITextField *)field{
UIToolbar *textFieldToolbar = [[UIToolbar alloc] init];
[textFieldToolbar sizeToFit];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(doneClick)];
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
[textFieldToolbar setItems:[NSArray arrayWithObjects:flexSpace, doneButton, nil] animated:NO];
field.inputAccessoryView = textFieldToolbar;
}
-(void)doneClick
{
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
}
#end
And then I call this from where I need it, like this:
#import "UIUtils.h"
UIUtils *utils = [[UIUtils alloc] init];
[utils addDefaultToolbar:field];
Now, unfortunately, the button doesn't work. The keyboard should be dismissed but it isn't. Secondly, I have no idea if I am doing this right.
How do I do this properly?
Edit:
Further research indicates that the doneClick method is not being called at all. It does work if I add doneClick to the viewController, but this kinda defeats the purpose of what I'm trying to achieve here.
What you want is a toolbar that takes care of its own button press. You can create one of those.
#interface MYDefaultToolBar : UIToolbar
#end
#implementation MYDefaultToolBar
- (instancetype)init
{
self = [self initWithFrame: CGRectZero];
if (self) {
[self sizeToFit];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self action:#selector(doneClick)];
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:self action:nil];
[self setItems:[NSArray arrayWithObjects:flexSpace, doneButton, nil] animated:NO];
}
return self;
}
-(void)doneClick
{
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
}
#end
Then you want to be able to add it to text fields. I'd probably recommend doing that by hand:
textField.inputAccessoryView = [MYDefaultToolBar new];
This is very clear that it's modifying the inputAccessoryView (so if something else in the same code also modifies the inputAccessoryView, it'll be more obvious that there's a conflict). But you can also do this with a category, particularly if it were more complex.
#interface UITextField (AddDefaultToolBar)
- (void)my_addDefaultToolBar;
#end
#implementation UITextField (AddDefaultToolBar)
- (void)my_addDefaultToolBar {
self.inputAccessoryView = [MYDefaultToolBar new];
}
Or you could modify -[MYDefaultToolBar init] to take a text field, and add itself.

How to set title Edit and Done for Button from UIBarButtonItem?

I have a problem,Top button does not change own name but it works
- (IBAction)switchEdit:(UIBarButtonItem *)sender {
sender.possibleTitles = [NSSet setWithObjects:#"Edit", #"Done", nil];
if (sender.style == UIBarButtonSystemItemEdit){
sender.style = UIBarButtonSystemItemDone;
[self.navigationItem.rightBarButtonItem setTitle:#"Done"];
self.tableView.editing = NO;
}else{
sender.style = UIBarButtonSystemItemEdit;
[self.navigationItem.rightBarButtonItem setTitle:#"Edit"];
self.tableView.editing = YES;
}
}
Consider using UIViewController's editButtonItem property, since it seems to do automatically what you need:
Returns a bar button item that toggles its title and associated state
between Edit and Done.
If you want to keep your approach, you can declare these two buttons as properties and set them up in viewDidLoad for example, like that:
#interface ViewController ()
#property (nonatomic, strong) UIBarButtonItem *editBarButtonItem;
#property (nonatomic, strong) UIBarButtonItem *doneBarButtonItem;
#end
and then
- (void)viewDidLoad
{
[super viewDidLoad];
self.editBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editSelectorName)];
self.doneBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(doneSelectorName)];
}
and then in your code just do:
self.navigationItem.rightBarButtonItem = self.editBarButtonItem
or
self.navigationItem.rightBarButtonItem = self.doneBarButtonItem

calling custom delegates not working

I have made a custom delegate:
Example.h:
#protocol DismissExamplePopoverDelegate
- (void) dismissExamplePopover;
- (int) getUserID;
#end
#interface Example : UIViewController{
id<DismissExamplePopoverDelegate> delegate;
}
#property (nonatomic, assign) id<DismissExamplePopoverDelegate> delegate;
It is called in Example.m like follows:
[[self delegate] getUserID];
In my maincontroller.h:
#import "Example.h"
#interface MainScreen : UIViewController<DismissExamplePopoverDelegate>
maincontroller.m:
-(int) getUserID
{
return 100;
}
the view Example is called by the following method:
ExampleController = [[Example alloc] initWithNibName:#"Example" bundle:nil];
ExamplePopoverController = [[UIPopoverController alloc] initWithContentViewController:ExampleController];
[ExampleController setDelegate:self];
ExamplePopoverController.popoverContentSize = CGSizeMake(600, 480);
if ([ExamplePopoverController isPopoverVisible]) {
[ExamplePopoverController dismissPopoverAnimated:YES];
} else {
CGRect popRect = CGRectMake((self.EditExampleSelectAddButtonProperty.frame.origin.x),
(self.EditExampleSelectAddButtonProperty.frame.origin.y),
(self.ExampleSelectAddButtonProperty.frame.size.width),
(self.ExampleSelectAddButtonProperty.frame.size.height));
[ExamplePopoverController presentPopoverFromRect:popRect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
If I place [[self delegate] getUserID] in any function other than viewdidload it works perfectly: returns 100; in viewdidload it returns 0.
What i want to achieve is a delegate to be called automatically when the popover loads. Is viewdidload the best place for it, or is there somewhere else
A couple of people have said this, but haven't been clear enough about the solution.
Instead of doing this:
ExampleController = [[Example alloc] initWithNibName:#"Example" bundle:nil];
ExamplePopoverController = [[UIPopoverController alloc] initWithContentViewController:ExampleController];
[ExampleController setDelegate:self];
Do this:
ExampleController = [[Example alloc] initWithNibName:#"Example" bundle:nil];
[ExampleController setDelegate:self];
ExamplePopoverController = [[UIPopoverController alloc] initWithContentViewController:ExampleController];
What's happening is that -[UIPopoverController initWithContentViewController:] uses its contentViewController's view, causing -[Example viewDidLoad] to be called. Since you haven't yet set the delegate at that point, the delegate is nil, and attempting to call any method on nil returns nil, 0, or 0.0.
Make sure you're actually assigning the delegate property to MainController. For instance, wherever you've initialized Example, you need to set the delegate property:
Example *example = [[Example alloc] init];
example.delegate = self; // Use this if you're initializing Example in MainController
If you're not initializing Example in MainController, instead of "self" use the MainController instance.
In Example.m you need to add line:
[self setDelegate:delegate];
Also, you need to #synthesize delegate;
Hope it help)

iPhone SDK -> RightBarButtonItem disappears when loading view through AppDelegate

So I have an issue with my rightbarbuttonitem disappearing. I have two ways of loading this view, first time launch it loads it after user enters in their name (from an initial view). Second time (after the app exits), it checks if the name exists in my stored database and if it does it loads the view right away. This second time is where the button does not show.
The button was set in viewDidLoad of my view originally here (and is still set here for the first load):
if (self.navigationItem.rightBarButtonItem == nil){
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(buttonPressed)];
self.navigationItem.rightBarButtonItem = addButton;
[self.navigationItem.rightBarButtonItem retain];
}
Then in the .m of my AppDelegate, I added the button to think that would resolve it on the second load:
if(success){
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
UIViewController *control = [[ViewController alloc] initWithNibName:#"myNib" bundle:nil];
[control retain];
UINavigationController *navControl = [[UINavigationController alloc] initWithRootViewController:control];
[navControl retain];
[self.window setRootViewController:navControl];
if (navControl.navigationItem.rightBarButtonItem == nil){
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(buttonPressed)];
navControl.navigationItem.rightBarButtonItem = addButton;
[navControl.navigationItem.rightBarButtonItem retain];
}
//[navControl release];
[self.window makeKeyAndVisible];
return;
Here's the declaration of the addButton in the header for App Delegate and my view's header:
UIBarButtonItem *addButton;
#property (nonatomic, retain) UIBarButtonItem *addButton
Other posts say to check viewDidLoad/viewWillAppear but putting that first blurb of code in either of those does not solve the issue.
Solved it myself, since the button was never added to the nib, initializing UIViewController doesn't help. I had to initialize my view controller.
In other words replace: UIViewController *control = [[ViewController alloc] initWithNibName:#"myNib" bundle:nil]; with: MyViewControllerClassName *control = [[MyViewControllerClassName alloc] initWithNibName:#"myNib" bundle:nil]; Hope this helps others in the future, thanks for taking the time to read this if you did!

when i release an NSMutableArray that i allocate and initiate, my code breaks

with this code, i want to add an image at the place the user taps. i want to add a new one for each tap.
-(void) foundDoubleTap:(UITapGestureRecognizer *) recognizer
{
UIView *piece = recognizer.view;
CGPoint locationInView = [recognizer locationInView:piece];
UIImageView *testPoint = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"inner-circle.png"]];
testPoint.frame = CGRectMake(0, 0, 20, 20);
testPoint.center = CGPointMake(locationInView.x, locationInView.y);
[self.imageView addSubview:testPoint];
NSMutableArray *tempTestPointArray = [[NSMutableArray alloc] initWithArray:testPointArray];
[tempTestPointArray addObject:testPoint];
testPointArray = tempTestPointArray;
NSLog(#"testPointArray: %#", testPointArray);
CGRect myRect = CGRectMake((testPoint.center.x + 12), (testPoint.center.y + 12), 10, 10);
UILabel *myLabel = [[UILabel alloc] initWithFrame:myRect];
myLabel.text = [NSString stringWithFormat:#"Point %d", [testPointArray count]];
myLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:10];
[myLabel sizeToFit];
[imageView addSubview:myLabel];
[myLabel release];
[testPoint release];
//[tempTestPointArray release];
}
why is it that when i release tempTestPointArray, my code breaks when i implement a second tap? it crashes on:
NSMutableArray *tempTestPointArray = [[NSMutableArray alloc] initWithArray:testPointArray];
when i comment out the release for it, the Analyzer does not flag it as a leak. what happened to the rule, if you alloc/init it, you have to release it?
EDIT: adding .h file
.h file:
#interface TestPointMapViewController : UIViewController <UIScrollViewDelegate, UITextFieldDelegate>
{
//other code
NSArray *testPointArray;
}
//other code
#property (nonatomic, retain) NSArray *testPointArray;
//other code
#end
and then #synthesize testPointArray in .m file.
Your testPointArray is not assigning to a property, it is a plain ivar. Doing the line
testPointArray = tempTestPointArray;
Is leaking whatever is previously in testPointArray. Declare testPointArray as a retained property and change to.
self.testPointArray = tempTestPointArray;
Then keep the [tempTestPointArray release];
EDIT:
So the reason why this code is failing has to do with the magic of properties. The following code is equivalent.
self.testPointArray = tempTestPointArray;
[self setTestPointArray:tempTestPointArray];
When you do the #sythesize testPointArray; it is generating a setter method similar to this:
- (void)setTestPointArray:(NSMutableArray *)array {
id temp = testPointArray;
testPointArray = [array retain];
[temp release];
}
So when you don't use the property notation self.testPointArray you are not retaining the variable correctly. You are also losing a reference to a retained object, which is now a leak.
That make sense? If not please review this.
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/objectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW1