I was thinking about this today, and now i've tested i'm a little confused…
When using viewControllers either by pushing a viewController onto the Navigation Stack or Presenting a ViewController modally I'm wondering about memory management.
Lets use the modal example as a thought experiment, here is the source to create and present the view, in my example it doesn't matter if ARC or not so here's both:
With ARC:
ViewController *myViewController = [[ViewController alloc] init];
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
Without ARC:
ViewController *myViewController = [[ViewController alloc] init];
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
[myViewController release]; //As it's now 'owned' by the presenting View controller
This would be my understanding about how to present a viewController modally over an existing ViewController.
Lets say for our example the above code resides in a method which is called when a button is touched to present the ViewController.
Now to my question,
What I am doing is calling this code each time a button is touched, During testing with Instruments I didn't seem to have any leaks. - However because I have NSLog statements in the myViewController dealloc & viewDidLoad methods I know that it's getting instanciated everytime I touch the button but never deallocated.
So...
A) Why am I not getting a leak showing (or a rise in Live Bytes) in instruments (when either using ARC or not) because I am seemingly creating a new viewController and leaking the old one each time I go to present it.
B) What is the correct way to write the above code if this is not memory safe? I see this kind of code snippets all over Apple's example code and internet. Should I (and they) not be wrapping the alloc init line in an if statement to check if the object is already created?
i.e.
if(!myViewController)
{
ViewController *myViewController = [[ViewController alloc] init];
}
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
Thanks for taking the time to read and answer, I really wonder about this as I've been creating, pushing and presenting ViewControllers using the above code the whole time, and never noticed a leak! - might have to go back and rewrite it all!
To avoid confusion please note: The delegate property is a custom property of my UIViewController subclass (where I've implemented a delegate protocol), required to dismiss the Modally present Viewcontroller properly. As per coding guidelines.
Regards,
John
EDIT As Requested, Creation of the delegate:
.h
#protocol NotificationManagementViewControllerDelegate;
#interface NotificationManagementController :
{
__weak NSObject <NotificationManagementViewControllerDelegate> *delegate;
}
#property (nonatomic, weak) NSObject <NotificationManagementViewControllerDelegate> *delegate;
#protocol NotificationManagementViewControllerDelegate <NSObject>
#optional
- (void)didFinishSettingNotification:(NotificationManagementController *)notificationManagementController;
.m
- (void)sendMessageToDismiss {
if ([[self delegate] respondsToSelector:#selector(didFinishSettingNotification:)]) {
[self.delegate didFinishSettingNotification:self];
}
}
And finally in the delegates .m:
- (void)didFinishSettingNotification:(NotificationManagementController *)notificationManagementController
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
You are not getting a leak because you create a new controller and ARC will release this allocation for you.
But, it's better to create a #property for your new view controller.
and modify your i.e. implementation like :
#property (nonatomic, strong) ViewController *myViewController;
if (!_myViewController)
self.myViewController = [[ViewController alloc] init];
self.myViewController.delegate = self;
[self presentViewController:_myViewController animated:YES completion:nil];
Here, you have a lazy property and you don't create a new one ViewController after the first creation.
But, you need to pass your delegate (or any property) outside your test.
Furthermore, if you use your first implementation and add this controller in a subview of the current controller without property, this will work but you will get a leak.
I got this experience with the code below :
RootViewController
- (void)viewDidLoad
{
[super viewDidLoad];
ViewController *myViewController = [[ViewController alloc] init];
[self.view addSubview:myViewController.view];
}
myViewController will be add on the screen but released immediately without keeping any reference of the object, so if you add an action in 'ViewController`, your application will crash without explanation of XCode.
So, the correct way to write this without leak will be :
- (void)viewDidLoad
{
[super viewDidLoad];
if (!_myViewController)
self.myViewController = [[ViewController alloc] init];
[self.view addSubview:self.myViewController.view];
}
The answer is a bit longer and can be improved so don't hesitate !
Hope it's going to help some people.
Related
I'm very new to Xcode and have run into some difficulties when opening new views.
I currently use this way to open a new view. It is stored in a utilities NSObject class:
+ (void)OpenSettings:(UIViewController *)VC{
Settings *settings = [[Settings alloc] initWithNibName:#"Settings" bundle:nil];
second.userID=[[[NSUserDefaults standardUserDefaults] objectForKey:#"UserID"] integerValue];
[VC.self presentViewController:settingsInst animated:NO completion:nil];
}
Is there anyway that I can open a view WITHOUT having to pass in the current view controller? It's not of huge concern but to me makes it look a little untidy when having to call the following in the classes that want to open up my settings view:
[Utilities OpenSettings:self];
(1) Because you said that you are new to Xcode: do you know Storyboards? Please have a look at it if you haven't already: https://developer.apple.com/library/ios/referencelibrary/GettingStarted/RoadMapiOS/SecondTutorial.html
As far as I understand, you are trying to encapsulate the "push settings view" functionality which could be very easy be made with segues in storyboards.
(2) If you do not want to use storyboards for whatever reason, you could write your own superclass that inherits from UIViewController:
MyViewController.h
#interface MyViewController : UIViewController
#end
MyViewController.m
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void) openSettings
{
Settings *settings = [[Settings alloc] initWithNibName:#"Settings" bundle:nil];
settings.userID=[[[NSUserDefaults standardUserDefaults] objectForKey:#"UserID"] integerValue];
[self presentViewController:settings animated:NO completion:nil];
}
#end
Any of your ViewController that inherit from MyViewController will now be able to use
[self openSettings]
Keep in mind, that UITableViewController is not a subcalls of UIViewController. If you want the same functionality here, you also have to create a custom MyTableViewController.
Exact solution depends on your view controller hierarchy. I'm assuming you have a UINavigationController on your active window (which are probably created in AppDelegate):
[self.navigationController.topViewController presentViewController:settings animated:NO completion:nil];
will do it. If you can post your main view structure, we can help more (Are you using a UITabBarController or UINavigationController or just a view etc.?)
I'm loading a viewController (LandingViewController) that's crashing because my 'ConnectionStatus' object is being deallocated for some reason by UIKit, and I'm trying to send a message to it.
My code is as below:
#property (nonatomic, strong) ConnectionStatus *cStatus;
- (void)viewDidLoad
{
[super viewDidLoad];
...
self.cStatus = [[ConnectionStatus alloc] init];
}
When I load up zombie instruments, I get this:
Is there a reason why UIKit is deallocating the object before my view controller even loads?
UPDATE (how I load this View Controller):
-(void)logout {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
LandingViewController *lvc = [storyboard instantiateViewControllerWithIdentifier:#"landingVC"];
[self presentViewController:lvc animated:YES completion:nil];
}
The viewDidLoad release is correct; that is ARC releasing the object you created and assigned to the strong property.
The problem looks to be to be either in your ProfileViewController, or in whatever notification is being sent. The object is a zombie at the point where you are posting a notification. You may be retaining something incorrectly or using it (or an owning object) as the notification object, but we'd need to see that code to be sure.
I have a custom ViewController which is an instance variable of my root viewController.
I intend to modally present it whenever a button is touch. Therefore the viewController will be presented and dismissed potentially many many times.
I obviously only want to alloc init my instance variable once as the modal viewController is not deallocated each time it's dismissed, so should I have code like this inside my button action to ensure that it's only alloc and inited once?:
if(!myViewController)
{
ViewController *myViewController = [[ViewController alloc] init];
}
[self presentViewController:myViewController animated:YES completion:NULL];
I usually use lazy instatiation in those cases:
Declare a property for your ViewController:
#property(nonatomic, strong) UIViewController *myViewController;
After that you can override the get of myViewController
-(UIViewController*) myViewController {
if(!_myViewController) {
_myViewController = [[UIViewController alloc] init];
}
return _myViewController;
}
This way you guarantee that was only instantiated once and is always there when you needed.
ATTENTION
This works well if you always use self.myViewController. I consider a good practice that properties' generated iVars should only be accessed in their setters/getters.
You can use the following way to ensure that only one instance of the view controller active at a time.
if(myViewController) {
[myViewController release];
myViewController = nil;
}
myViewController = [[ViewController alloc] init];
[self presentViewController:myViewController animated:YES completion:NULL];
You need to make myViewController as class variable.
I have two views a levelComplete view and a levelSelector view. What I would like to do is when the levelComplete shows or the ViewDidLoad occurs on that view I would like to send a delegate to the level selector to show a button in the view and then make that button UserInteractionEnabled so then I will then be able to programme that button to do something if its not hidden.
You want to necessarily do it via a delegate. Coz you can do it in a simpler way as well. When you call your secondView, just tell your button to hide. So your modified code to calling the second view controller becomes:
-(IBAction)passdata:(id)sender {
secondview *second = [[secondview alloc] initWithNibName:nil bundle:nil];
self.secondviewData = second;
sender.hidden=YES;
secondviewData.passedValue = textfield.text;
[self presentModalViewController:second animated:YES];
}
And then you can set it to visible when your view loads up again using viewDidLoad. I can tell you how to do it via delegates if you need. Lemme know what works best.
EDIT - Solution by Delegates
Your secondView's Header file will be as follows:
#protocol SecondViewHandlerDelegate <NSObject>
- (void)viewHasBeenLoaded:(BOOL)success;
#end
#interface secondview :UIViewController {
IBOutlet UILabel *label;
NSString *passedValue;
}
#property (nonatomic, retain)NSString *passedValue;
-(IBAction)back:(id)sender;
#end
Then, in the implementation file of secondView(.m), synthesise the delegate first by #synthesize delegate; . After this, in your viewDidLoad of secondView, add the following line:
[[self delegate] viewHasBeenLoaded:YES];
That should be enough for your secondView. Now onto the firstViewController, perform the following steps:
In the header file (.h), import your second view and implement the protocol:
#interface ViewController :UIViewController <SecondViewHandlerDelegate>{
..
..
}
In the implementation file (.m) of your firstViewController, implement this method:
- (void)viewHasBeenLoaded:(BOOL)success
{
NSLog("Delegate Method Called");
[myButton setHidden:YES];
}
And finally, in your code when you call the secondView, add this line:
secondview *second = [[secondview alloc] initWithNibName:nil bundle:nil];
second.delegate = self;
...
That should solve your purpose. I'd appreciate if you could mark the answer as correct as well. Thanks :)
I am working on an application where i am pushing one view controller on to a UINavigationController and releasing it immediately as the navigation controller retains it.When i am poping the view controller the dealloc method is being called as expected but the problem is the app is getting crashed.If i observe in GDB by enabling NSZombie its saying that -[MyViewController isKindOfClass:]: message sent to deallocated instance 0x6847a00.If i remove [super dealloc]from my view controller's deallocmethod its working just fine.I have nothing else in dealloc method except [super dealloc].What could be the problem here, can any one please help.The code snippet is below:
MyViewController *myViewController = [[MyViewController alloc] initWithNibName:nil bundle:nil];
myViewController.path = selectedPath; //very important to set path here
myViewController.parentViewController = self;
[self cleanBookshelf];
[self.navigationController pushViewController:myViewController animated:NO];
[myViewController release];
[indicatorView removeFromSuperview];
[loadingindicator stopAnimating];
and i am poping in one action method of myViewController as
-(IBAction)goBack:(UIButton*)sender{
[self.navigationController popViewControllerAnimated:YES];
}
Just guessing, but I suspect that the problem is with this line:
myViewController.parentViewController = self;
UIViewController's parentViewController property is marked readonly, and I'd take that as a strong message that you shouldn't mess with it.