Lazy-loading a view in a UIViewController subclass - objective-c

I have a UIViewController that should lazily load a view and then keep it in memory as it's re-used quite often until e.g. a memory warning occurs or I want to clean it for some other reason. In order to achieve the lazy loading, I've overwritten the default getter. Here's my code:
#interface MyController {
MyView *_myView;
}
#property(nonatomic, retain) MyView *myView;
#end
#implementation MyController
#synthesize myView = _myView;
- (MyView *)myView {
if(_myView == nil) {
_myView = [[MyView alloc] init];
// some more initialization
}
return _myView;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// the main part of my interest, freeing myView again
self.myView = nil;
}
#end
Now my question is basically: Is it still correct to release myView like I did in - (void)didReceiveMemoryWarning or would I have to [_myView release]; _myView = nil; or even something completely different?
Also, is this generally the correct way of using lazy initialization or should I improve something here in general?

Your statement for releasing the memory is absolutely correct and there is no difference between two.
self.myView = nil;
In this case the setter method for myView property will be called and similar to as below.
-(void) setMyView:(MyView*) aMyView {
[myView release];
myView = aMyView;
}
for the lazy loading ... I guess -(void)viewDidDisappear for releasing the views and others memory related stuff and -(void) loadview to recreate them again

If I am not mistaken the default UIViewController behaviour already loads the view lazily, see the documentation for -loadView:
You should never call this method directly. The view controller calls
this method when the view property is requested but is currently nil.

Related

NSTableView is not being displayed

This is a follow-up on the previous question.
Sorry. I could not figure out how to add code or edit something written over 5 minues ago.
A brief summary. I am trying to display a customized/derived TableView over a regular View. I am not using IB, but doing everything from the code. The goal here is to build the application, but also to learn Cocoa/OSX programming. This is my first OSX coding attempt.
NSView atop of which I would like to display my custom TableView is being displayed fine. Please excuse the NSLog garbage. It helps me to learn about the app lifecycle.
Header:
#import <Cocoa/Cocoa.h>
#import "MSNavigationTableView.h"
#interface MSNavigationPanelView : NSView
#property (strong, nonatomic) IBOutlet MSNavigationTableView *myNavigationTable;
#end
code:
#import "MSNavigationPanelView.h"
#implementation MSNavigationPanelView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
NSLog(#"Initializing Navigation Panel");
}
self.myNavigationTable = [[MSNavigationTableView alloc] initWithFrame:self.frame];
[self.myNavigationTable setDataSource:self.myNavigationTable];
[self.myNavigationTable setDelegate:self.myNavigationTable];
[self addSubview:self.myNavigationTable];
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"Drawing navigation view!");
}
#end
Now the NSTableView derived class.
Header:
#import <Cocoa/Cocoa.h>
#interface MSNavigationTableView : NSTableView <NSTableViewDataSource>
#end
NSArray *myNavigationArray;
Source:
#import "MSNavigationTableView.h"
#implementation MSNavigationTableView
+ (void)initialize {
NSLog(#"Called NavigationTableView::initialize!");
myNavigationArray = [NSArray arrayWithObjects:#"Call History" #"Contacts", #"Messages", #"Voicemail", nil];
}
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
- (NSInteger)numberOfRowsInTableView: (NSTableView *) aTableView
{
return [myNavigationArray count];
}
- (id)tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
NSLog([myNavigationArray objectAtIndex:rowIndex]);
return [myNavigationArray objectAtIndex:rowIndex];
}
#end
Thank you. I am sure that I am doing something stupid, and/or perhaps not doing something necessary. I have tried to figure this out for a couple of hours. No ideas so far.
You really need to use Interface Builder to make a table.
I would never try to programmatically initialized a table... to many things to configure.
NSTableView needs to have NSTableColumns, NSTableColumns need to have NSCell's, etc. etc.
NSTableView needs to be embedded in an NSScrollView.
I figured out what needs to be done.
First, array initialization has to be moved from +(void)initialize to another method. For me - (id)initWithFrame works fine.
Second, while this was not clear for me, overwriting NSTableViewDataSource is not enough.
One has to create NSTableColumn(s) then add the column(s) to the table using addTableColumn method of NSTableView class. Once that is done, we proceed with setDataSource and so on.

IBOutlets properties. Release or not release

I'm developing an iOS 4 application.
I have this ViewController:
#interface BlogViewController : UIViewController
{
...
UIView* tabBar;
}
#property (nonatomic, retain) IBOutlet UIView* tabBar;
And its implementation:
And its implementation:
#implementation BlogViewController
#synthesize tabBar;
- (void) dealloc
{
...
[super dealloc];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.tabBar = nil;
}
My question if I have a IBOutlet property, is it necessary to declare the UIView like this?
#interface BlogViewController : UIViewController
{
...
UIView* tabBar;
}
If I do it, Do I need to release it on dealloc?
- (void) dealloc
{
...
[tabBar release];
[super dealloc];
}
In order: no you don't need to declare the instance variable, yes you do need to release the object. You may consider using Automatic Reference Counting to get the memory management aspect correct for you.
Yes, you do need to release an IBOutlet, if you retained it.
However, IBOutlets are owned by their nib file, so the usual practice is to use an assign or weak #property instead of retain or strong. In which case you don't need to release it.
If you are using xcode 4+ then while creating the outlet it itself makes a release for the same in dealloc and viewDidUnload so no need to do it again.

Can I use segues with designated initializers of view controllers?

I am new to storyboards and I have set up a segue from a button to a view controller. This view controller is of a custom subclass SFListViewController, which has a designated initializer initWithList:.
Using the designated initializer is the only way to correctly initialize the view controller. However, when using segues the designated initializer won't be called (obviously).
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Show List"]) {
SFListViewController *listViewController = segue.destinationViewController;
// ????
}
}
How can I make the segue call the designated initializer when performed?
As far as I know this is not possible since the storyboard framework simply calls alloc and init on the class you define in Interfacebuilder. Additionally the segue's destinationViewController attribute is read-only so you couldn't simply replace the existing ViewController either.
The only way to use Storyboarding would probably be to create a wrapper-class that internally instantiates the SFListViewController with the desired attributes and then functions as a proxy object and thus propagates viewDid*** and viewWill***-methods to the wrapped class and also returning the wrapped VC's view in a readonly view property... You get the idea.
Generally there are a number of alternative ways to initialize a UIViewController in such a case:
There is an option to specify "User defined runtime Attributes" which could be used for initialisation.
Override the prepareForSegue: method, like you tried, in your root ViewController and do "post-alloc-init-initialisation" there.
If worst comes worst, you could fall back to an IBAction in order to be able to initialize the ViewController yourself.
I hope this helps.
Edit: I can verify that the Proxy-Approach works since I just came across a similar problem with ABPeoplePickerNavigationController where this approach worked nicely. Since we've set up the thing in our story board please note that you have to use awakeFromNib in order to do initial configuration (instead of some init method).
This is the code for my wrapper class:
#import "PeoplePickerViewControllerWrapper.h"
#implementation PeoplePickerViewControllerWrapper
#synthesize ppvc = _ppvc; // This is the object I'm proxying (The proxyee so to speak)
#synthesize delegate = _delegate;
- (void)awakeFromNib
{
self.ppvc = [[ABPeoplePickerNavigationController alloc] init ];
self.ppvc.peoplePickerDelegate = self;
self.ppvc.addressBook = ABAddressBookCreate();
self.ppvc.displayedProperties = [NSArray arrayWithObject:[NSNumber numberWithInt:kABPersonPhoneProperty]];
}
#pragma mark - View lifecycle
- (void)loadView
{
[super loadView];
[self.ppvc loadView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.ppvc viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.ppvc viewWillAppear:animated];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.ppvc viewDidDisappear:animated];
}
-(UIView *)view{
return self.ppvc.view;
}
- (void)viewDidUnload
{
[super viewDidUnload];
[self.ppvc viewDidUnload];
}

Within a view controller, is it necessary to call removeFromSuperview on subviews during viewDidUnload?

Many people say during a view controller's viewDidUnload method you must remove subviews by calling the removeFromSuperview method. For example, Three20 does something like this:
- (void)viewDidUnload {
[super viewDidUnload];
... snipped ...
[_tableBannerView removeFromSuperview];
TT_RELEASE_SAFELY(_tableBannerView);
[_tableOverlayView removeFromSuperview];
TT_RELEASE_SAFELY(_tableOverlayView);
... snipped ...
}
I understand the reasoning behind this belief: if you call [self.view addSubview:_aView] in loadView, you should call [_aView removeFromSuperview] in viewDidUnload. The thing is, this doesn't seem necessary. When a view controller's view gets released, its dealloc method automatically releases all of its subviews. My test code shows subviews automatically get released when their superview gets released:
#interface TestView : UIView
#end
#implementation TestView
- (id)retain {
NSLog(#"view retain");
return [super retain];
}
- (void)release {
NSLog(#"view release");
[super release];
}
- (id)init {
NSLog(#"view init");
return (self = [super init]);
}
- (void)dealloc {
NSLog(#"view dealloc");
[super dealloc];
}
#end
#interface TestViewController : UINavigationController
#end
#implementation TestViewController
- (void)loadView {
NSLog(#"viewController loadView");
[super loadView];
[self.view addSubview:[[TestView new] autorelease]];
}
- (void)viewDidUnload {
NSLog(#"viewController viewDidUnload");
[super viewDidUnload];
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(#"viewDidAppear");
[super viewDidAppear:animated];
[self dismissModalViewControllerAnimated:YES];
}
- (void)dealloc {
NSLog(#"viewController dealloc");
[super dealloc];
}
#end
The above code produces the following output:
viewController loadView
view init
view retain
view release
viewDidAppear
viewController dealloc
view release
view dealloc
As you can see, when the view controller's main view gets released, its subviews also get released.
Also, the iOS Developer Library [states](http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html#//apple_ref/doc/uid/TP40007457-CH101-SW4
): "In the case of a low-memory condition, the default UIViewController behavior is to release the view object stored in the view property if that view is not currently being used." Also: "If you use a declared property to store a reference to your view, and that property uses retain semantics, assigning a nil value to it is enough to release the view."
So, if releasing a view automatically releases its subview, is it really necessary to call removeFromSuperview during viewDidUnload?
No, it isn't necessary, the dealloc, as you quite rightly said, will do that for you :) (long post, short answer).
I found it to be necessary in my project. My viewController has a main view (as they all do) and in this case it is defined using a xib (not programmatically allocated and added as a subview). This view has subviews with IBOutlets in the view controller. If, on viewDidUnload, I simply set the IBOutlet properties to nil ( self.mySubView = nil ), then dealloc on that subview is not called. If I first remove it from it's superview (the main view), then dealloc is called.

Bringing up new view controllers - releasing query

I've written some code where I bring up a new view (from my main view controller); then it calls the main controller when it is closed, like so -
-(void)showMyNewView {
MyNewViewController *myNewViewController = [[MyNewViewController alloc] initWithNibName:#"MyNewViewController" delegate:self];
[self.view addSubview:myNewViewController.view];
}
and then when the new one closes, it calls -
-(void)myNewViewControllerDidFinish:(MyNewViewController *)myNewViewController {
[myNewViewController.view removeFromSuperview];
[myNewViewController release];
}
Now this works fine, and there are no leaks, but the compiler moans with warnings about "Potential leak of an object allocated on line x and stored into myNewViewController".
I've been looking at Apple's presentModalViewController:animated: code, which also doesn't release the new modal view controller in the method which creates it, it seems to release it with a dismissModalViewControllerAnimated: call when the delegate's viewControllerDidFinish: method is called. Is there something I'm missing here? Using the presentModalViewController code doesn't generate any warnings. Many thanks for any help.
I think I've figured it out now, and I've written a small bit of code which gives me my own version of "presentModalViewController:animated:" with all the control I want. I'd be grateful to hear what more seasoned coders make of this (it's probably really straight forward but I've not been doing this for very long...), and if there are any problems with the code, etc -
Interface:
#import <UIKit/UIKit.h>
enum {
MyViewLoaderTransitionTypeNone = 0,
MyViewLoaderTransitionTypeSomeEffect,
MyViewLoaderTransitionTypeSomeOtherEffect
};
typedef NSInteger MyViewLoaderTransitionType;
#interface MyViewLoader : UIViewController {
UIViewController *myLoadedViewController;
}
#property (nonatomic, retain) UIViewController *myLoadedViewController;
-(void)loadView:(UIViewController *)theViewController withTransition:(MyViewLoaderTransitionType)theTransition;
-(void)dismissViewWithTransition:(MyViewLoaderTransitionType)theTransition;
#end
Implementation:
#import "MyViewLoader.h"
#implementation MyViewLoader
#synthesize myLoadedViewController;
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
;
}
return self;
}
-(void)dealloc {
[myLoadedViewController release];
[super dealloc];
}
-(void)loadView:(UIViewController *)theViewController withTransition:(MyViewLoaderTransitionType)theTransition {
[self setLoadedViewController:theViewController];
UIView *theLoadedView = theViewController.view;
[self.view addSubview:theLoadedView];
// do all sorts of transition stuff here
[theViewController viewWillAppear:NO];
}
-(void)dismissViewWithTransition:(MyViewLoaderTransitionType)theTransition {
UIView *theLoadedView = self.loadedViewController.view;
// do all sorts of transition stuff here
[theLoadedView removeFromSuperview];
self.loadedViewController = nil
}
I just use MyViewLoader as the superclass of any view controllers where I need it.
Thanks for any comments / help!
The usual thing to do here is, when you add a subview to a view, release the subview directly after. The parent view becomes responsible for the subview. When removeFromSuperview is called later, that decrements the retain count and the subview is automatically released.