Modal view using UIModalPresentationFormSheet stays in memory (ARC) - objective-c

I'm presenting a modal view with the UIModalPresentationFormSheet presentation style.
MPMediaItemCollection *albumItem = [self.albums objectAtIndex:index];
AlbumViewController *destination = [[AlbumViewController alloc] initWithAlbum:albumItem];
destination.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:destination animated:NO];
When the user taps outside the modal view, it is closed. However, it's still in memory, it's not released.
What's the correct way to deal with this?
Edit: is seems there's something inside AlbumViewController that's not being released properly. A bug on my end.

Yes, this is correct. ARC will take care of deallocating the memory when it is needed.
From here:
ARC deallocs any object to which there are no more strong references. So to dealloc something, simply set all the variables pointing to it to nil and make sure the object is not involved in any circular reference.

Related

Correct method to present a different NSViewController in NSWindow

I am developing an app that is a single NSWindow and clicking a button inside the window will present a NSViewController, and a button exists in that controller that will present a different NSViewController. I know how to swap out views in the window, but I ran into an issue trying to do this with the multiple view controllers. I have resolved the issue, but I don't believe I am accomplishing this behavior in an appropriate way.
I originally defined a method in the AppDelegate:
- (void)displayViewcontroller:(NSViewController *)viewController {
BOOL ended = [self.window makeFirstResponder:self.window];
if (!ended) {
NSBeep();
return;
}
[self.box setContentView:viewController.view];
}
I set up a target/action for an NSButton to the AppDelegate, and here's where I call that method to show a new view controller:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self displayViewcontroller:newVC];
}
This does work - it presents the new view controller's view. However if I then click any button in that view that has a target/action set up that resides within its view controller class, the app instantly crashes.
To resolve this issue, I have to change didTapContinue: to the following:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self.viewControllers addObject:newVC];
[self displayViewcontroller:[self.viewControllers lastObject]];
}
First of all, can you explain why that resolves the issue? Seems to be related to the way the controller is "held onto" in memory but I'm not positive.
My question is, how do I set this up so that I can swap out views from within any view controller? I was planning on getting a reference to the AppDelegate and calling displayViewcontroller: with a new controller I just instantiated in that class, but this causes the crash. I need to first store it in the array then send that reference into the method. Is that a valid approach - make the viewControllers array public then call that method with the lastObject, or how should this be set up?
What is interesting in your code is that you alloc/init a new view controller every time that you call the IBAction. It can be that your view its totally new every time you call the IBAction method, but I would think that you only have a limited number of views you want to show. As far as my knowledge goes this makes your view only to live as long as your IBAction method is long. That the view still exists, is because you haven't refreshed it. However, calling a method inside a view controller that is not in the heap anymore (since you left the IBAction method and all local objects, such as your view controller are taken of the heap thans to ARC) makes the app crash, because you reference a memory space that is not in use or used by something else.
Why does the app work when you ad the view to the viewcontrollers array? I assume this array is an array that has been initiated in the AppDelegate and now you add the view controller with a strong reference count to the viewcontrollers array. When you leave the IBAction method, the view controller still has a strong reference and ARC will not deallocate the view controller.
Is this the proper way? Well, it works. I would not think it is considered very good programming, since you don't alloc/init an object in a method that needs to stay alive after leaving the method. It would be better practice to allocate and initialize your view controller(s) somewhere in an init, awakeFromNIB or a windowDidLoad method of your AppDelegate. The problem with your current solution is that you are creating an endless array of view controllers of which you only use the last. Somewhere your program will feel the burden of this enormously long array of pretty heavy objects (view controllers) and will run out of memory.
Hope this helps.
By the way, this is independent of whether you use Mavericks or Yosemite. I was thinking in a storyboard solution, but that wouldn't answer your question.
Kind regards,
MacUserT

How correctly release UITabController

This may sound a newbie question ... but however I'm new to iOS development.
I've created UITabController object programmatically like this.
mTabBarController = [[UITabBarController alloc] init];
...
mTabBarController.viewControllers = [NSArray arrayWithArray:tabBarItems];
[tabBarItems release];
And releasing mTabBarController in dealloc like this.
- (void)dealloc {
[mTabBarController release];
}
Now my question : will I get a memory leak ? When I assign value t viewController the ref count of tabBarItems is still 1. When I release mTabBarController does it also release all its viewcontrollers ?
Yes, the tab controller owns an array of view controllers (and everything in an array is retained). You're not creating a leak as long as you properly release or autorelease the items you're adding to the tabBarItems array.
It really helps to think of object relationships as ownerships.
UITabBarController should never be placed as the child of another ViewController, so you will always have to release it in dealloc. If your TabBarController's view is the childview of your application's window, it's ok not to release it in dealloc since the only time dealloc will ever get called is when your program is closing, in which case your controller will be released anyways. However, some people like to release it in dealloc anyways just to keep their code consistent. What you're doing is fine.

objective-c addSubView retain count

I was under the impression that adding a subview to a view goes like this:
UITableViewController *sitesel = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped];
sitesel.view.frame = CGRectMake(0,0,100,100);
[self.left addSubview:sitesel.view];
[sitesel release];
But it seems I should not release the sitesel (the controller)?
So should I release the view or what, I had this retain stuff nailed a while ago, but it's slipped. (And to use a TableView, you have to subclass UITableViewController right?)
(self.left is a subview of self.view, added in a nib)
addSubview does retain the view, that's not the problem. Your issue is that the controller for the view goes away a little later.
You shouldn't release the view, because that's none of your business. You didn't create it, you didn't touch it. Leave it alone.
In order to keep things working, it needs to stay connected to a valid controller. Hence, you must not release the controller, but keep it around. Add a property like #property(retain) UITableViewController *siteController; and then do self.siteController = sitesel; before you release the controller. This way everything stays in memory.
PS: For cleanness, you should probably change the view in the accessor for sitesel. Just to make sure it always comes and goes along the controller. Your method would then get even shorter, just setting the controller.
ADDED: That setter could look like that, requiring you to set only the controller and the view being updated transparently:
- (void)setSiteselController:(UITableViewController *)ctrl {
if (_sitesel)
[_sitesel.view removeFromSuperview];
[_sitesel autorelease];
_sitesel = [ctrl retain];
if (_sitesel) {
_sitesel.view.frame = CGRectMake(0,0,100,100);
[self.left addSubview: _sitesel.view];
}
}
Your original code will then shrink to this much cleaner version:
UITableViewController *sitesel = [[UITableViewController alloc] initWithStyle: UITableViewStyleGrouped];
self.siteselController = sitesel;
[sitesel release];
PPS: You don need an controller for a UITableView to work. It's just much simpler!

Why is this object being deallocated?

I'm developing an iPhone app, I'm trying to push a view into the navigation controller, which I've done many times before, however, I'm having some issues with this particular app. I have a table view, and when the user selects one row the new view is pushed into the controller:
DataWrapper *row=[[self.rows objectAtIndex:[indexPath section]] objectAtIndex:[indexPath row]];
DataViewController *nextController=[[DataViewController alloc] initWithNibName:#"Data" bundle:[NSBundle mainBundle]];
[nextController setInfo:row];
[nextController setRow:[indexPath row]];
[nextController setParent:self];
[self.navigationController pushViewController:nextController animated:YES];
[nextController release];
and it goes fine, until the user taps the back button, I get an exception, and used NSZombieEnabled and get this:
-[DataViewController respondsToSelector:]: message sent to deallocated instance 0x4637a00
So i tried to remove the [nextController release] and in fact it worked, but WHY???? I allocated nextController, so I'm supposed to release it, right?? I don't feel right releasing this app if there's something like this, I feel like it's going to fail. Please let me know your thoughts.
Your nextController isn't being retained by navigation controller. If you release it then because there is only one init/release pair, the object is deallocated. Later when the navigationController attempts to send messages to it, you get the error you see.
This is also why remove [nextController release] fixes the problem.
You are right in that if you allocated, you should free it. But the caveat is only after your application is done with it, not before.
Some objects will stay allocated for nearly the lifetime of the application, so don't feel too bad.
I would guess that [self.navigationController] is returning nil, because if it weren't nil, it would be retaining your object. Since your object is not getting retained, it would appear that there is no object that's trying to retain it, indicating that the navigationController property is empty.
Is it possible that your navigation controller is somehow being deallocated, resulting in the view controllers also getting released? You could maybe test it by retaining the nav controller just before pushing nextController.
For debugging purposes, I would override -dealloc in your DataViewController class, and set a breakpoint on it.
but it seems to work for something like:
DetailViewController *controller = [[DetailViewController alloc] initWithNibName:#"SomeView" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController: controller animated:NO];
[controller release];
so, why is it not working for pushViewController ?

Placing elements into the UITableView footer, when to release them?

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
AViewController * aView = [[AViewController alloc] initWithNibName:#"myNib" bundle:[NSBundle mainBundle]];
return vintagePriceRowView.view;
}
I know this code needs a release... somewhere. But where? If I set the allocated AviewController to autorelease, then touching elements within the view results in a "message sent to deallocated instance 0xfb5780"
The Leaks instrument is not actually showing a leak, but obviously Clang does not like the above code. I know Clang is not the gospel as far as determining everything that could possibly be wrong in your code, but in this case, it feels like it is probably right. I've allocated it, I need to release it.
Any ideas on what I am doing wrong?
For efficiency's sake, you should create the view that you'll be using in the footer before it's required. Maybe create it in viewDidLoad in your tableViewController and store it in a member variable.
Then in your viewForFooter method simply return the view you stored earlier.
Then in your tableViewController's dealloc method, release the view.
You need to hold on to it until it is not longer needed.
I suggest making a private property, set it to nil initially, and then lazy load the nib and assign the returned view to the property. Then in dealloc or in viewDidUnload simply set it to nil via the setter.
You will of course need to release or autorelease once you assigned it to the private property, since the setter will retain it for you.