Nib files store the application’s user interface (views) in “freeze-dried” state. When you initialize a view controller with the appropriate Nib file, a copy of the outlet objects is created from the “frozen” one in the Nib file and the IBOutlet ivars in your class are hooked up to point to the newly created objects.
This unfreezing happens every time the view is loaded. Each time the view is resurrected in this manner it will appear like the original in the Nib file. So if you load a view and then make modifications (say change the layout) when the view is unloaded and reloaded, the modifications will not be visible.
From the resource programming guide:
“Nib files are the documents produced by the Interface Builder application. A nib file describes the visual elements of your application’s user interface, including windows, views, controls, and many others. It can also describe non-visual elements, such as the objects in your application that manage your windows and views. Most importantly, a nib file describes these objects exactly as they were configured in Interface Builder. At runtime, these descriptions are used to recreate the objects and their configuration inside your application. When you load a nib file at runtime, you get an exact replica of the objects that were in your Interface Builder document. The nib-loading code instantiates the objects, configures them, and reestablishes any inter-object connections that you created in Interface Builder.”
Contents of a Nib file:
Interface objects: These are the user interface elements in the application – views, windows, buttons, tables etc. Interface objects can also represent non-visual objects like controller objects that your application uses to manage the visual objects.
File’s Owner: This is a proxy object and represents the object that you will create in code and pass to the the Nib loading code. If your Nib file and the class of your object have the same name then simply calling alloc and init for your object will automatically load the correct Nib file. For example, consider the following files:
MyTableViewController.mMyTableViewController.h
MyTableViewController.xib
Here, I assume MyTableViewController.{h,m} is a class derived from UITableViewController (or NSTableViewController). When an object of this type is created by calling
MyTableViewController *mtvc = [[MyTableViewController alloc] init];
Automatically, the object is associated with the nib file named MyTableViewController.xib and the corresponding elements from the nib file are loaded and the outlets in the object are hooked up.
This behavior can be changed in the following way – open the nib file in interface builder. Select “File’s Owner” object and in the identity inspector, set the class to any class in your code.
First Responder: This is also a proxy object and represents the first object in the application’s dynamic responder chain. For more information about the responder chain and how it is used to dispatch events in AppKit–based applications, see Event Architecture in Cocoa Event-Handling Guide. For information about the responder chains and handling actions in iPhone applications, see Event Handling Guide for iOS.
Note: Only a very simple application should store all its user interface components in a single nib file. It is better to distribute components across multiple nib files. Creating smaller nib files allows you to load only required portions of the interface.
Memory management and Nib file objects:
Each time a nib file is loaded the underlying code creates a NEW copy of the objects in the file. This means that your code is responsible for releasing the objects when it is done. How this needs to be done varies depending on the platform and memory model:
Mac OS X managed memory model: Top level objects in the nib file have a positive retain count. Objects that have parents are retained by their respective parents and do not have additional retain counts. Your code is responsible only for releasing the top level objects.
Mac OS X garbage collected memory model: Objects in the graph are kept in memory through strong memory references. But top level objects do not have strong references. Your code is responsible to create the strong reference to top level objects to prevent the object graph from being released.
iOS managed memory model: Objects are retained either using a setter method (setValue:forKey:0 or by default (if no setter is available). Objects in the graph that are not at the top level are retained by their parents (created autoreleased so they will have retain counts equal to the number of parents). Any objects that are hooked up to your class using IBOutlets will have additional retain counts. Your code is responsible for releasing all top level objects as well as all outlets.
Important note:
There is a difference in Mac OS X and iOS managed memory models. In the former, all objects are retained and they are connected via the object graph to the top level objects. So sending release to the top level objects will “propagate” the release to the other objects in the graph. In iOS, in addition to top level objects all IBOutlets have to also be released.
Good programming conventions:
Always have outlets for your top level objects and define setter methods to retain and release them. Remember to release all your outlets in -dealloc as well as -viewDidUnload. Doing this will make your code portable across platforms. Example:
@interface MyController: UIViewController {
IBOutlet UITextField *textField;
}@property(retain) IBOutlet UITextField *textField;
@end@implementation MyController
@synthesize textField;-(void) viewDidUnload {
self.textField = nil;
}-(void) dealloc {
self.textField = nil;
[super dealloc];
}
@end
This will ensure that IBOutlets are always retained in both iOS and Mac OS X (managed memory model) platforms. Your code will have to release the outlets in -viewDidUnload (iOS only) as well as -dealloc. Note that setting the textField ivar to nil will release it (the setter method will release the ivar before setting the new value) and also set it to nil.
The above implementation leaves IBOutlets exposed to “tampering”. So someone else using your code could in theory access your outlets and mess with them (say displaying something in the text field). You can get some protection against that using the implementation below where the @property is moved to the implementation file.
MyController.h
@interface MyController: UIViewController {
IBOutlet UITextField *textField;
}@end
MyController.m
@interface MyController()
@property(retain) IBOutlet UITextField *textField;
@end@implementation MyController
@synthesize textField;-(void) viewDidUnload {
self.textField = nil;
}-(void) dealloc {
self.textField = nil;
[super dealloc];
}@end
Since code distributions (library, frameworks etc) usually include only a compiled binary along with header files the @property will be hidden from other users of your code. But remember that there is nothing “private” about methods in objective-c. If a MyController object (with class defined like above) gets a -setTextField: or -textField message it will respond and do the right thing. The only “private” part is that without access to MyController.m one would not know the setter exists!!
Another (popularly called out) nit in the above code is about calling an instance method from -dealloc (i.e. the setter). Usually it is not a good practice to call instance methods from -dealloc because within this method the state of your object is undefined. There are no guarantees that the instance method can behave correctly. So the usual preferred way to implement -dealloc is
-(void)dealloc {
[textField release];
[super dealloc];
}
While it is usually good practice not to call instance methods from -dealloc in this instance I believe it is safe unless someone inherits from MyController and decides to override -setTextField: to do something funky!! But sometimes it is tempting to avoid this convention for the following convenience (put in big scary note in .h file warning against overriding setters):
MyController.m
@interface MyController()
@property(retain) IBOutlet UITextField *textField;
@end@implementation MyController
@synthesize textField;-(void) releaseOutlets {
self.textField = nil;
}-(void) viewDidUnload {
[self releaseOutlets];
}-(void) dealloc {
[self releaseOutlets];
[super dealloc];
}@end
In iOS, each time you add an IBOutlet you have to make sure you remember to release it in two methods. Easier to do the above and save some trouble.
Reference:
