Archive for June 23, 2013
iPhone app startup: AppDelgates, MainWindow.xib and some code
0When beginners learn to write programs in C, they learn that main()
is the starting point of their program. But that is only partially correct. There is stuff that happens before main()
is called. The first thing to run is the C runtime, which is in a library called crt.o
. It allocates space for global and static variables, it copies initial values to initialized variables, and it initializes all static variables to 0. Then it calls your main()
. At the end, it takes your return code from main() and passes it back to the operating system.
AppDelegates: your app’s front door
Similarly, iPhone apps have a startup and shutdown sequence that is handled by iOS. At certain points, it will dip into your code to determine what to do. The primary interface that you have to control this is the AppDelegate. The AppDelegate protocol has several methods that can be used to notify your app that something is about to happen (low memory, you’re going into the background, a notification of a time change or location change, etc). It also has some methods where you can specify your preferences (which orientations does your app support, what should the status bar look like). It’s a lot richer than just a simple call to main()
, but the concept is the same. The OS is doing stuff, and when it needs you, these methods are the entry points into your app.
If you do not set your AppDelegate up properly, your app may fail to load, or it may simply show a black screen instead of your views.
I recently discovered this while trying to “modernize” an early XIB-based app that has been in the store for a few years. Somewhere in the process, I messed up this startup sequence, and I was greeted with a plain black screen. Here is what I found out about the startup sequence.
You actually do include a main()
in your code. Usually, it is very small. This is the main.m
from my XIB-based app.
#import <UIKit/UIKit.h> int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, nil); } }
The main thing it does it set up an autoreleasepool which keeps track of memory pointers and is used for garbage collection. Then it calls UIApplicationMain()
. The first two arguments are familiar, but the next two are used if you want to override default behavior.
- The third argument is where you can specify the name of your own Application subclass.
- The fourth argument is where you can specify the name of your own class that will implement the AppDelegate protocol. We’ll talk about that below.
At this point, there are two ways your app can start up: through XIB files created in Interface Builder, or in Objective-C code.
XIB-based app startup
When the fourth argument to UIApplicationMain()
is nil
, the application will look in your plist file for a Main Storyboard or a Main Interface. You’ll see these in XCode under “target” in the “summary” tab. This will be the name of a XIB file.
It loads this XIB file, and looks for a class that will be your AppDelegate and a window that will be your main application window.
There are many tutorials on setting up these XIB files. The one that I used is at http://www.trappers.tk/site/2011/06/16/mainwindow-xib/.
In a nutshell, this is what you do. Note that all of my terminology is probably wrong here, because I don’t use Interface Builder much.
- Create an empty IB document.
- Set the Owner to a UIApplication class.
- Add an “Object” to the canvas, make it of
myAppDelegate
class. - Add a “Window” to the canvas.
- You already have a skeleton AppDelegate class (.h and .m files), edit the .h file to contain the key word “IBOutlet” in the window declaration. Interface Builder uses this to identify code that it can hook to objects in the XIB.
- Connect the AppDelegate object to the File Owner’s “delegate” hookup.
- Connect the Window object to the AppDelegate’s “window” hookup.
- Then edit your project target, in the “summary” tab, go to “Main Interface” and select your XIB file from the pulldown.
I find that using a GUI drawing tool to set up this sort of plumbing is confusing, but the gist of it is that you are creating an association between this class that you have (probably called myAppDelegate), a window, and the app’s AppDelegate protocol.
When the app starts up, it runs your main()
, calls UIApplicationMain()
, sees the nil
in the fourth argument slot and decides that you want to use the XIB method. It looks in the myapp-Info.plist and finds the MainWindow XIB file, loads that XIB, loads that window, finds out which class is your AppDelegate, and then calls application:didFinishLaunchingWithOptions:
on that class.
Programmatic app startup
Instead, I choose to implement most of my plumbing in Objective-C code, for the following reasons:
- I find the pictorial representation of these abstract connections to be more confusing than their code equivalents.
- It’s difficult to reproduce something when the steps are “control-drag the whoosit to the whatsit over there”.
- Code is much easier to copy and compare.
- This is a biggie — XIB file formats change with new tool releases, and so sometimes you’re left with no way forward. An example is when I used XCode5 (iOS7 tools) to create a XIB file, but could not use it at all under XCode 4.2 (iOS6 tools).
- I get all starry-eyed when I use a plain text editor.
Here are the three files that you must set up to get this same startup process working using Objective-C.
main.m
#import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain( argc, argv, // command-line args nil, // Application class NSStringFromClass([AppDelegate class]) // AppDelegate ); } }
AppDelegate.h
#import <UIKit/UIKit.h> #import "MyappModel.h" @interface AppDelegate : UIResponder <UIApplicationDelegate> { UINavigationController * navController; MyappModel * myappModel; } @property (nonatomic, retain) UIWindow * window; @property (nonatomic, retain) MyappModel * myappModel; @end
AppDelegate.m
#import "AppDelegate.h" #import "MyScreen1VC.h" #import "MyScreen2VC.h" #import "MyappModel.h" @implementation AppDelegate // create getters and setters for @properties @synthesize window = _window; @synthesize myappModel; - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // My application model myappModel = [[MyappModel alloc] init]; // Create a root window. Application delegates are expected to have one. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor lightGrayColor]; // Create a navigation controller, attach it to the window as the "root". navController = [[UINavigationController alloc] init]; [self.window setRootViewController: navController]; // create a view controller for the first view MyScreen1VC * vc = [[MyScreen1VC alloc] init]; // Push the first view controller on the stack. [navController pushViewController:vc animated:NO]; // Add the navigation controller’s view to the window [self.window addSubview:navController.view]; // Display the main window. [self.window makeKeyAndVisible]; } @end
So how does this one work?
When the app starts up, it runs your main()
, calls UIApplicationMain()
, sees the name of your AppDelegate class in the fourth argument slot and sees that you will be handing app delegation yourself. It calls application:didFinishLaunchingWithOptions:
, which you have implemented in your AppDelegate. Now it is up to you to allocate a window, create a controller and assign it as the root controller, and then make the window “key and visible”.
Conclusion
Whether you prefer to use Interface Builder or Objective-C code to create your views, it is important to understand the sequence of events that takes place when your app starts up. If you miss one of these steps, it could cause your app to fail to launch. I recently encountered the dreaded “black screen of nothing”, and it caused me to scratch a little deeper to see what was going on.
I hope this tutorial clarifies some of this delicate dance in your mind.