detected potential leak by analyze - ios

the following is my code:
UIImage *takePhotoImg = [UIImage imageNamed:#"add_pic.png"];
self.takePhoto = [[UIButton alloc] initWithFrame:CGRectMake(120, 100, takePhotoImg.size.width, takePhotoImg.size.height)];
[_takePhoto setImage:takePhotoImg forState:UIControlStateNormal];
[_takePhoto addTarget:self action:#selector(takePhotoBtn) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_takePhoto];
when I use analyze,it shows the line:
[_takePhoto setImage:takePhotoImg forState:UIControlStateNormal];
Potential leak of an object allocated.
Did I need add release,or just ignore?
thank you in advance
UPDATE:
I did have released the button _takePhoto in my dealloc:
-(void)dealloc
{
[_takePhoto release];
[super dealloc];
}
my property :
#property(nonatomic,retain)UIButton *takePhoto;

If you're not using ARC then yes, you need to release it. When you add it to self.view's subview, self.view will retain it so the button will not go away.
If you don't need to access the button and don't need to hold a reference, you can release it right after the addSubview: call.
However, it looks like takePhoto is a property on your class. If that's the case and you want to hold a reference to the button for later use, just add the [_takePhoto release] call to your class dealloc method. That should surpress the code analysis warning.

change code:
self.takePhoto = [[UIButton alloc] initWithFrame:CGRectMake(120, 100, takePhotoImg.size.width, takePhotoImg.size.height)];
to
self.takePhoto = [[[UIButton alloc] initWithFrame:CGRectMake(120, 100, takePhotoImg.size.width, takePhotoImg.size.height)] autorelease];
[_takePhoto release] in delloc method is for the retain in setter method. Every time you call self.takePhoto = aNewTakePhote, aNewTakePhote will be retained once.

Related

Unit testing button click is not working

I have added a button programmatically and I have to run unit test for automation process. We dont have much UI components so we dont use UI testing bundle.
Added button in code
self.proceedButton.frame = CGRectMake(0.0, 0.0, (buttonContainerWidth * 0.75), (buttonContainerHeight * 0.3));
self.proceedButton.center = CGPointMake(self.buttonContainer.center.x, CGRectGetHeight(self.proceedButton.frame));
self.proceedButton.layer.cornerRadius = CGRectGetHeight(self.proceedButton.frame) / 2;
self.proceedButton.layer.masksToBounds = YES;
self.proceedButton.titleLabel.font = proceedFont;
[self.proceedButton addTarget:self action:#selector(onAcceptClicked) forControlEvents:UIControlEventTouchUpInside];
Test:
[vc viewDidLoad];
NSString *selector = [[vc.proceedButton actionsForTarget:vc forControlEvent:UIControlEventTouchUpInside] firstObject];
XCTAssert([selector isEqualToString:#"onAcceptClicked"]);
[vc.proceedButton sendActionsForControlEvents: UIControlEventTouchUpInside];
This fails if I comment out the [self.proceedButton addTarget:self action:#selector(onAcceptClicked) forControlEvents:UIControlEventTouchUpInside]; line so it seems the test is written correctly.
However sendActionsForControlEvents: is not entering onAcceptClicked method in the test.
Why this is happening? Is it possible that unit test is finishing before onAcceptClicked getting actually called?
This could be because you are calling [vc viewDidLoad] directly, which Apple advises you not to do, presumably because a bunch of setup is performed before viewDidLoad is eventually invoked.
You should never call this method directly. The view controller calls this method when its view property is requested but is currently nil. This method loads or creates a view and assigns it to the view property.
Instead, try using XCTAssertNotNil(vc.view); at the beginning. This will try to load the view, eventually calling viewDidLoad for you and probably setting up your button so that the actions are correctly added to it.
Edit So if it helps, I've just quickly run this test and I can verify that the method onAcceptClicked is called in the view controller when the test runs.
Button setup in viewDidLoad
CGRect frame = CGRectMake(50, 40, 30, 30);
self.proceedButton = [[UIButton alloc] initWithFrame:frame];
self.proceedButton.titleLabel.text = #"button!!!!";
[self.proceedButton addTarget:self action:#selector(onAcceptClicked) forControlEvents:UIControlEventTouchUpInside];
self.proceedButton.backgroundColor = [UIColor blackColor];
[self.view addSubview:self.proceedButton];
Test
-(void)testButton {
self.myVC = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"MyVC"];
XCTAssertNotNil(self.myVC.view);
NSString *selector = [[self.myVC.proceedButton actionsForTarget:self.myVC forControlEvent:UIControlEventTouchUpInside] firstObject];
XCTAssert([selector isEqualToString:#"onAcceptClicked"]);
[self.myVC.proceedButton sendActionsForControlEvents: UIControlEventTouchUpInside];
}

iOS unit testing button has action assigned

I've got the following button written programmatically:
self.button = [[UIButton alloc] init];
[self.button setTitle:#"Reverse String!" forState:UIControlStateNormal];
self.button.backgroundColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1.0];
self.button.layer.cornerRadius = 5.0;
[self.button addTarget:self action:#selector(reverseString:) forControlEvents:UIControlEventTouchUpInside];
However, my unit test fails when I try to test:
-(void)testButtonActionAssigned
{
XCTAssert([self.vc.button respondsToSelector:#selector(reverseString:)]);
}
I get a failed message saying:
test failure: -[ViewControllerTests testButtonActionAssigned] failed:
(([self.vc.button respondsToSelector:#selector(reverseString:)]) is
true) failed
My setup method is called:
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
self.vc = [[ViewController alloc] init];
NSLog(#"THIS METHOD IS CALLED");
}
Is it to do with the simulator timing and startup ?
[self.button addTarget:self action:#selector(reverseString:) forControlEvents:UIControlEventTouchUpInside];
First line means that reverseString: method should be in your ViewController (which is specified by target: parameter and you pass self there). So you need to change test with just removing .button
-(void)testButtonActionAssigned
{
XCTAssert([self.vc respondsToSelector:#selector(reverseString:)]);
}
Here is small example which can be changed:
UIButton *button = [UIButton new];
[button addTarget:self action:#selector(doThat:) forControlEvents:UIControlEventTouchUpInside];
NSString *selectorString = [[button actionsForTarget:self forControlEvent:UIControlEventTouchUpInside] firstObject];
SEL sel = NSSelectorFromString(selectorString);
// so now you can check if target responds to selector
// BOOL responds = [self respondsToSelector:sel];
NSLog(#"%#", selectorString);
I got it. I was testing the wrong thing.
The code in the question isn't really testing the button has an action assigned. It's basically saying "does the view controller responds to this method" or "does the view controller have this method".
// this is saying "does the ViewController know about this method"
// rather than "is ViewController's button assigned method reverseString:"
XCTAssert([self.vc respondsToSelector:...]);
This means regardless of whether button has a selector assigned or not, as long as the method is defined, this assert is always true.
I verified it by commenting out the line of code:
[self.button addTarget:self action:#selector(reverseString:) forControlEvents:UIControlEventTouchUpInside];
and the test still passes, which isn't what I was trying to achieve.
The proper test should be:
-(void)testButtonActionAssigned
{
[self.vc viewDidLoad];
NSArray *arrSelectors = [self.vc.button actionsForTarget:self.vc forControlEvent:UIControlEventTouchUpInside];
NSString *selector = nil;
if(arrSelectors.count > 0)
{
selector = arrSelectors[0];
}
XCTAssert([selector isEqualToString:#"reverseString:"]);
}
Now after commenting out the line:
[self.button addTarget:self action:#selector(reverseString:) forControlEvents:UIControlEventTouchUpInside];
The test fails as expected :D
This proper test code fetches the selector from the button and checks to see if it indeed is the method called reverseString:
EDIT
After B.S. answer, and confirming with Apple's documentation, a shorter version can be written as:
-(void)testButtonActionAssigned
{
[self.vc viewDidLoad];
NSString *selector = [[self.vc.button actionsForTarget:self.vc forControlEvent:UIControlEventTouchUpInside] firstObject];
XCTAssert([selector isEqualToString:#"reverseString:"]);
}
I was originally worried that calling firstObject on an empty array would cause an app to crash but Apple's documentation has confirmed to me that firstObject is a property that returns nil if array is empty.
The first object in the array. (read-only)
Declaration #property(nonatomic, readonly) ObjectType firstObject
Discussion If the array is empty, returns nil.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/#//apple_ref/occ/instp/NSArray/firstObject

Adding views to the current viewcontroller from another class

I have a UIViewController named MainViewController. I have an another class named ViewMaker.
In the ViewMaker class I have a function as follows.
-(UIView *)GetContentView
{
UIView *return_view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
UIButton *done_button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
done_button.frame = CGRectMake(300, 300, 100, 50);
[done_button setTitle:#"Done" forState:UIControlStateNormal];
[return_view addSubview:done_button];
return return_view;
}
In the above function actually I will add more views like textviews, labels etc.
In the MainViewController class I am calling the above method as follows.
-(void)CreateViews
{
UIView *content_view = [[[ViewMaker alloc] init] GetContentView];
[self.view addSubview:content_view];
}
Now I have added a view with done button(and other components like textviews, labels etc) from the MainViewController class.
My problem is about adding a target function for the done button. When I click the done button I want to perform some actions(like add some views) in the MainViewController according to the datas in the view that was returned from the ViewMaker class.
How can I do this?
Thanks in advance.
I assume your ViewMaker class is same as PopupContentMaker,
add this to your done button:
[done_button addTarget:self action:#selector(doSomething) forControlEvents:UIControlEventTouchUpInside];
in PopupContenMaker:
- (void)doSomething{
if ([self.delegate respondsToSelector:#selector(handleDoneButtonTap)]) {
[self.delegate performSelector:#selector(handleDoneButtonTap) withObject:self];
}
}
declare a delegate property in your PopupContentMaker, and make MainViewController the delegate,
-(void)GetContentView
{
PopContentMaker *pcm = [[PopupContentMaker alloc] init];
pcm.delegate = self;
[self.view addSubview:[pcm GetContentView]];
}
-(void)handleDoneButtonTap{
//Do action after done button
}
You can try:
[done_button addTarget: self
action: #selector(buttonClickedMethod)
forControlEvents: UIControlEventTouchUpInside];
In this way you can call a custom method when button is pressed.
You can pass the target and action as parameters to the getContentView method or you can use the delegate pattern.

showing a UITextField that is a local variable vs a property in a custom view

I am mocking up a quick demo of a project but am having a problem with a UITextField.
The behavior that we want is that when a user clicks on a button, there should be a custom view that appears with a UITextField and a UIButton in a custom view that overlays the main view.
I have a custom view called Searchview and the following in the Searchview.m. The problem is that when the textField is a property, it doesn't show but when it is a local variable, it does show. Can anybody help me with what is going on so that the UITextField shows? Is how I am doing this even the right idea (custom UIView or custom UIControl or a modal controller)? Finally, would setNeedsDisplay be appropriate here?
thx in advance
#interface Searchview()
#property (nonatomic, weak) UITextField *textField;
#end
- (void)drawRect:(CGRect)rect
{
// this doesn't work
self.textField = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 120.0f, 25.0f)];
self.textField.returnKeyType = UIReturnKeyDone;
self.textField.placeholder = #"Writer";
self.textField.borderStyle=UITextBorderStyleBezel;
[self.textField addTarget:self
action:#selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit];
[self addSubview: self.textField];
/* this works
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 120, 25)];
textField.returnKeyType = UIReturnKeyDone;
textField.placeholder = #"Writer";
textField.borderStyle=UITextBorderStyleBezel;
[textField addTarget:self
action:#selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit];
[self addSubview: textField];
*/
UIButton *mButton=[UIButton buttonWithType:UIButtonTypeRoundedRect];
mButton.frame=CGRectMake(200.0f,10.0f,100.0f,37.0f);
[mButton setTitle:#"search" forState:UIControlStateNormal];
[mButton addTarget:self action:#selector(showSearchController:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:mButton];
[self setNeedsDisplay];
}
As a property - not showing:
As a local variable - showing:
#property (nonatomic, strong) UITextField *textField;
change the weak to strong and change the self.textFiled to _textField to have a try
And make sure your textField property not be released
It's pretty simple when you think about it. ARC (approximately) converts the following code:
self.weakProp = [[Foo alloc] init];
to the equivalent of the following "manually reference-counted" code:
Foo * temp = [[Foo alloc] init];
self.weakProp = temp;
[temp release];
Nothing is retaining it, so it is released.
I can only think of two reasons to have assign/weak IBOutlets:
For an outlet in a VC, so it doesn't retain a subview when its view is set to nil (e.g. on a memory warning). This is less relevant in iOS 6.0 since views are not automatically released on a memory warning (so if you do it, you can release them all explicitly).
For a view where the outlet points to a superview (and would cause a retain cycle). This is quite rare.
In general, I prefer strong IBOutlets: They might keep objects alive for a little longer than necessary, but they are safer than assign and more efficient than weak. Just watch out for retain cycles!

Memory hanging around after dismissModalViewControllerAnimated:YES

I am presenting a modal view controller which contains a UIScrollView with a bunch of images. When I dismiss my modal view controller I can see in my Allocations (instruments) that memory assigned to the images is hanging around but not causing leaks.
Causing me some confusion and any help would be great. Here is what I have...
UIViewController - DollViewController: This is my main view controller that I am presenting my modal over. Here is the code -
-(IBAction)openDollCloset {
NSLog(#"Open Closet");
[self playSound:#"soundMenuClick"];
ClosetViewController *closet = [[ClosetViewController alloc] initWithNibName:#"ClosetViewController" bundle:nil];
closet.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentModalViewController: closet animated: YES];
closet.delegate = self;
closet.addToCloset = NO;
[closet setCurrentDoll:myDoll];
[closet openCloset];
[closet release];
[self closeSubmenu];
}
I create my scrollview manually to hold the outfit images.
The object closetScroller is defined with #property (nonatomic, retain) UIScrollView *closetScroller; with a release in dealloc.
- (void)setScrollerValues {
if (closetScroller == nil)
{
self.closetScroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 400)];
[self.closetScroller setDecelerationRate:UIScrollViewDecelerationRateFast];
[self.closetScroller setDelegate:self];
[self.closetScroller setPagingEnabled:YES];
[self.closetScroller setShowsHorizontalScrollIndicator:NO];
[self.view addSubview:closetScroller];
}
[self.closetScroller setContentSize:CGSizeMake(outfitCount * 320, 400)];
}
After creating the scrollView I add the images to the scroller. I believe I am releasing everything right. I am creating the images with NSData objects and releasing them.
- (void)addOutfitsToScroller {
// Clear out any old images in case we just deleted an outfit
if ([[closetScroller subviews] count] > 0)
{
CATransition *fade = [CATransition animation];
[fade setDelegate:self];
[fade setType:kCATransitionReveal];
[fade setSubtype:kCATransitionFromRight];
[fade setDuration:0.40f];
[[self.closetScroller subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
[[closetScroller layer] addAnimation:fade forKey:#"FadeButtons"];
}
// Set the pageControl to same number of buttons
[pageControl setNumberOfPages:outfitCount];
// Load the outfit image and add it to the scroller
for (int i = 0; i < outfitCount; i++)
{
NSArray *savedDataArray = [[NSArray alloc] initWithArray:[closetItemsArray objectAtIndex:i]];
UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(320*i, 0, 320, 400)];
[v setUserInteractionEnabled:NO];
[v setAlpha:0.40f];
NSData *imageData = [[NSData alloc] initWithContentsOfFile:[savedDataArray objectAtIndex:1]];
UIImage *outfitImage = [[UIImage alloc] initWithData:imageData];
UIImageView *closetImage = [[UIImageView alloc] initWithImage:outfitImage];
[imageData release];
[outfitImage release];
[savedDataArray release];
CGSize frameOffset;
#ifdef LITE_VERSION
frameOffset = CGSizeMake(40, 60);
#else
frameOffset = CGSizeMake(20, 10);
#endif
closetImage.frame = CGRectOffset(closetImage.frame, frameOffset.width , frameOffset.height);
[v addSubview:closetImage];
[closetImage release];
[self.closetScroller addSubview:v];
[v release];
}
}
This all works great. Then when the user selects the "Close" button or selects an outfit I call:
[self.delegate closeClosetWindow];
Which calls the delegate method in the parentViewController which simply makes this call:
[self dismissModalViewControllerAnimated:YES];
I have also tried calling dismissModalViewControllerAnimated:YES from the modal view controller itself. It simply forwards the message to it's parentViewController and closes the modal just the same as the first way. Neither makes a difference with regards to the memory.
So what I do instead is use this method to close the view:
[[self.closetScroller subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
[self dismissModalViewControllerAnimated:YES];
When I do that, I can see my Object Allocations decrease in Instruments. Prior to opening the closet view I have object alloc at about 1.85mb. When I open the closet it increases to 2.5 or so depending on how many outfit images are loaded into the UIScrollView. Then when I close the view without the above method it stays at 2.5 and increases the next time I open it. Although when using the above method that removes the outfit images from the scroller, my object alloc drops back down close the the 1.85mb. Maybe a little more which tells me it's hanging on to some other stuff also.
Also note that when looking at the object allocations, I do see the ClosetViewController object created and when I close the modal it's refrence is released. I can see it malloc and free the view as I open and close it. It's not retaining the view object itself. i'm so confused.
Not sure what to do here. I am not getting any leaks and I am releasing everything just fine. Any ideas would be great.
mark
I Noticed object alloc in instruments keeping a live reference to UIScrollView every time I opened modal window. So to fix, when allocating for my scrollview, I instead allocated a new UIScrollView object and assigned it to closetScroller and then released. This took care of the problem
I think you were simply not releasing closetScroller in the dealloc of your view controller.

Resources