Every iPhone application is built using the UIKit framework and therefore has essentially the same core architecture. UIKit provides the key objects needed to run the application and to coordinate the handling of user input and the display of content on the screen. Where applications deviate from one another is in how they configure these default objects and also where they incorporate custom objects to augment their application’s user interface and behavior.
Although customizations to your application’s user interface and basic behavior occur down within your application’s custom code, there are many customizations that you must make at the highest levels of the application. Because these application-level customizations affect the way your application interacts with the system and other applications installed on a device, it is important to understand when you need to act and when the default behavior is sufficient. This chapter provides an overview of the core application architecture and the high-level customization points to help you make determinations about when to customize and when to use the default behavior.
From the time your application is launched by the user, to the time it exits, the UIKit framework manages the majority of the application’s key infrastructure. An iPhone application receives events continuously from the system and must respond to those events. Receiving the events is the job of the UIApplication object but responding to the events is the responsibility of your custom code. In order to understand where you need to respond to events, though, it helps to understand a little about the overall life cycle and event cycles of an iPhone application. The following sections describe these cycles and also provide a summary of some of the key design patterns used throughout the development of iPhone applications.
The application life cycle constitutes the sequence of events that occurs between the launch and termination of your application. In iPhone OS, the user launches your application by tapping its icon on the Home screen. Shortly after the tap occurs, the system displays some transitional graphics and proceeds to launch your application by calling its main function. From this point on, the bulk of the initialization work is handed over to UIKit, which loads the application’s user interface and readies its event loop. During the event loop, UIKit coordinates the delivery of events to your custom objects and responds to commands issued by your application. When the user performs an action that would cause your application to quit, UIKit notifies your application and begins the termination process.
Figure 1-1 depicts the simplified life cycle of an iPhone application. This diagram shows the sequence of events that occur from the time the application starts up to the time it quits. At initialization and termination, UIKit sends specific messages to the application delegate object to let it know what is happening. During the event loop, UIKit dispatches events to your application’s custom event handlers. Handling initialization and termination events is discussed later in “Initialization and Termination,” and the event handling process is introduced in“The Event-Handling Cycle” and covered in more detail in later chapters.
In an iPhone application, the main function is used only minimally. Most of the actual work needed to run the application is handled by the UIApplicationMain function instead. As a result, when you start a new application project in Xcode, every project template provides an implementation of the standard main function like the one in “Handling Critical Application Tasks.” The main routine does only three things: it creates an autorelease pool, it calls UIApplicationMain, and it releases the autorelease pool. With few exceptions, you should never change the implementation of this function.
Listing 1-1 The main function of an iPhone application
#import <UIKit/UIKit.h> |
int main(int argc, char *argv[]) |
{ |
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; |
int retVal = UIApplicationMain(argc, argv, nil, nil); |
[pool release]; |
return retVal; |
} |
The UIApplicationMain function at the heart of the preceding listing takes four parameters and uses them to initialize the application. Although you should never have to change the default values passed into this function, it is worth explaining their purpose in terms of starting the application. In addition to the argc and argv parameters passed into main, this function takes two string parameters that identify the principal class (that is, the class of the application object) and the class of the application delegate. If the value of the principal class string is nil, UIKit uses the UIApplication class by default. If the value of the application delegate’s class is nil, UIKit assumes that the application delegate is one of the objects loaded from your application’s main nib file (which is the case for applications built using the Xcode templates). Setting either of these parameters to a non-nil value causes the UIApplicationMain function to create an instance of the corresponding class during application startup and use it for the indicated purpose. Thus, if your application uses a custom subclass of UIApplication (which is not recommended, but certainly possible), you would specify your custom class name in the third parameter.
Monitoring the high-level behavior of your application is the responsibility of the application delegate object, which is a custom object that you provide. Delegation is a mechanism used to avoid subclassing complex UIKit objects, such as the default UIApplication object. Instead of subclassing and overriding methods, you use the complex object unmodified and put your custom code inside the delegate object. As interesting events occur, the complex object sends messages to your delegate object. You can use these “hooks” to execute your custom code and implement the behavior you need.
Important: The delegate design pattern is intended to save you time and effort when creating applications and is therefore a very important pattern to understand. For an overview of the key design patterns used by iPhone applications, see “Fundamental Design Patterns.” For a more detailed description of delegation and other UIKit design patterns, see Cocoa Fundamentals Guide.
The application delegate object is responsible for handling several critical system messages and must be present in every iPhone application. The object can be an instance of any class you like, as long as it adopts the UIApplicationDelegate protocol. The methods of this protocol define the hooks into the application life cycle and are your way of implementing custom behavior. Although you are not required to implement all of the methods, every application delegate should implement the methods described in “Handling Critical Application Tasks.”
For additional information about the methods of the UIApplicationDelegate protocol, see UIApplicationDelegate Protocol Reference.
Another task that occurs at initialization time is the loading of the application’s main nib file. If the application’s information property list (Info.plist) file contains the NSMainNibFile key, the UIApplication object loads the nib file specified by that key as part of its initialization process. The main nib file is the only nib file that is loaded for you automatically; however, you can load additional nib files later as needed.
Nib files are disk-based resource files that store a snapshot of one or more objects. The main nib file of an iPhone application typically contains a window object, the application delegate object, and perhaps one or more other key objects for managing the window. Loading a nib file reconstitutes the objects in the nib file, converting each object from its on-disk representation to an actual in-memory version that can be manipulated by your application. Objects loaded from nib files are no different than the objects you create programmatically. For user interfaces, however, it is often more convenient to create the objects associated with your user interface graphically (using the Interface Builder application) and store them in nib files rather than create them programmatically.
For more information about nib files and their use in iPhone applications, see “Nib Files.” For additional information about how to specify your application’s main nib file, see “The Information Property List.”
After the UIApplicationMain function initializes the application, it starts the infrastructure needed to manage the application’s event and drawing cycle, which is depicted in Figure 1-2. As the user interacts with a device, iPhone OS detects touch events and places them in the application’s event queue. The event-handling infrastructure of the UIApplication object takes each event off the top of this queue and delivers it to the object that best suited to handle it. For example, a touch event occurring in a button would be delivered to the corresponding button object. Events can also be delivered to controller objects and other objects indirectly responsible for handling touch events in the application.
In the iPhone OS Multi-Touch event model, touch data is encapsulated in a single event object (UIEvent). To track individual touches, the event object contains touch objects (UITouch), one for each finger that is touching the screen. As the user places fingers on the screen, moves them around, and finally removes them from the screen, the system reports the changes for each finger in the corresponding touch object.
When it launches an application, the system creates both a process and a single thread for that application. This initial thread becomes the application’s main thread and is where the UIApplication object sets up the main run loop and configures the application’s event-handling code. Figure 1-3 shows the relationship of the event-handling code to the main run loop. Touch events sent by the system are queued until they can be processed by the application’s main run loop.
NSRunLoop class of the Foundation framework. For more on NSRunLoop and run loops in general, see Threading Programming Guide.The UIApplication object configures the main run loop with an input source that processes touch events by dispatching them to the appropriate responder objects. A responder object is an object that inherits from the UIResponder class and that implements one or more methods for processing the different phases of a touch event. Responder objects in an application include instances of UIApplication, UIWindow, UIView, and all UIView subclasses. The application typically dispatches events to the UIWindow object representing the application’s main window. The window object, in turn, forwards the event to its first responder, which is typically the view object (UIView) on which the touch took place.
In addition to defining the methods you use to handle events, the UIResponder class also defines the programmatic structure of the responder chain, which is the Cocoa mechanism for cooperative event handling. The responder chain is a linked series of responder objects in an application, which usually starts at the first responder. If the first responder object cannot handle the event, it passes it to the next responder in the chain. The message continues traveling up the chain—toward higher-level responder objects such as the window, the application, and the application’s delegate—until the event is handled. If the event isn't handled, it is discarded.
The responder object that handles the event tends to set in motion a series of programmatic actions that result in the application redrawing all or a portion of its user interface (as well as other possible outcomes, such as the playing of a sound). For example, a control object (that is, a subclass of UIControl) handles an event by sending an action message to another object, typically the controller that manages the current set of active views. While processing the action message, the controller might change the user interface or adjust the position of views in ways that require some of those views to redraw themselves. When this happens, the view and graphics infrastructure takes over and processes the required redraw events in the most efficient manner possible.
For more information about events, responders, and how you handle events in your own custom objects, see “Event Handling.” For information about how windows and views fit into the event-handling scheme, see “The View Interaction Model.” For additional information about the graphics infrastructure and how views are updated, see “The View Drawing Cycle.”
The design of the UIKit framework incorporates many of the design patterns found in Cocoa applications in Mac OS X. Understanding these design patterns is crucial to creating iPhone applications, so it is worth taking a few moments to learn about them. The following sections provide a brief overview of these design patterns.
Design pattern | Description |
|---|---|
The Model-View-Controller (MVC) design pattern is a way of dividing your code into independent functional areas. The model portion defines your application’s underlying data engine and is responsible for maintaining the integrity of that data. The view portion defines the user interface for your application and has no explicit knowledge of the origin of data displayed in that interface. The controller portion acts as a bridge between the model and view and facilitates updates between them. | |
The delegation design pattern is a way of modifying complex objects without subclassing them. Instead of subclassing, you use the complex object as is and put any custom code for modifying the behavior of that object inside a separate object, which is referred to as the delegate object. At pre-defined times, the complex object then calls the methods of the delegate object to give it a chance to run its custom code. | |
Target-action | Controls use the target-action design pattern to notify your application of user interactions. When the user interacts with a control in a predefined way (such as by tapping a button), the control sends a message (the action) to an object you specify (the target). Upon receiving the action message, the target object can then respond in an appropriate manner (such as by updating application state in response to the button push). |
The Objective-C language uses a reference-counted scheme for determining when to release objects from memory. When an object is first created, it is given a reference count of 1. Other objects can then use the |
For a more thorough discussion of these design patterns, see Cocoa Fundamentals Guide.
The runtime environment of iPhone OS is designed for the fast and secure execution of programs. The following sections describe the key aspects of this runtime environment and provide guidance on how best to operate within it.
The strength of iPhone OS–based devices is their immediacy. A typical user pulls a device out of a pocket or bag and uses it for a few seconds, or maybe a few minutes, before putting it away again. The user might be taking a phone call, looking up a contact, changing the current song, or getting some piece of information during that time.
In iPhone OS, only one foreground application runs at a time. This means that every time the user taps your application’s icon on the Home screen, your application must launch and initialize itself quickly to minimize the delay. If your application takes a long time to launch, the user may be less inclined to use it.
In addition to launching quickly, your application must be prepared to exit quickly too. Whenever the user leaves the context of your application, whether by pressing the Home button or by using a feature that opens content in another application, iPhone OS tells your application to quit. At that time, you need to save any unsaved changes to disk and exit as quickly as possible. If your application takes more than 5 seconds to quit, the system may terminate it outright.
Even though your application does not run in the background when the user switches to another application, you are encouraged to make it appear as if that is the case. When your application quits, you should save out information about your application’s current state in addition to any unsaved data. At launch time, you should look for this state information and use it to restore your application to the state it was in when it was last used. Doing so provides a more consistent user experience by putting the user right back where they were when they last used your application. Saving the user’s place in this way also saves time by potentially eliminating the need to navigate back through multiple screens’ worth of information each time an application is launched.
For security reasons, iPhone OS restricts an application (including its preferences and data) to a unique location in the file system. This restriction is part of the security feature known as the application’s “sandbox.” The sandbox is a set of fine-grained controls limiting an application’s access to files, preferences, network resources, hardware, and so on. In iPhone OS, an application and its data reside in a secure location that no other application can access. When an application is installed, the system computes a unique opaque identifier for the application. Using a root application directory and this identifier, the system constructs a path to the application’s home directory. Thus an application’s home directory could be depicted as having the following structure:
/ApplicationRoot/ApplicationID/
During the installation process, the system creates the application’s home directory and several key subdirectories, configures the application sandbox, and copies the application bundle to the home directory. The use of a unique location for each application and its data simplifies backup-and-restore operations, application updates, and uninstallation. For more information about the application-specific directories created for each application and about application updates and backup-and-restore operations, see “File and Data Management.”
Important: The sandbox limits the damage an attacker can cause to other applications and to the system, but it cannot prevent attacks from happening. In other words, the sandbox does not protect your application from direct attacks by malicious entities. For example, if there is an exploitable buffer overflow in your input-handling code and you fail to validate user input, an attacker might still be able to crash your program or use it to execute the attacker’s code.
To manage program memory, iPhone OS uses essentially the same virtual memory system found in Mac OS X. In iPhone OS, each program still has its own virtual address space, but (unlike Mac OS X) its usable virtual memory is constrained by the amount of physical memory available. This is because iPhone OS does not write volatile pages to disk when memory gets full. Instead, the virtual memory system frees up volatile memory, as needed, to make sure the running application has the space it needs. It does this by removing memory pages that are not being used and that contain read-only contents, such as code pages. Such pages can always be loaded back into memory later if they are needed again.
If memory continues to be constrained, the system may also send notifications to the running applications, asking them to free up additional memory. All applications should respond to this notification and do their part to help relieve the memory pressure. For information on how to handle such notifications in your application, see “Observing Low-Memory Warnings.”
One way iPhone OS attempts to save power is through the automatic sleep timer. If the system does not detect touch events for an extended period of time, it dims the screen initially and eventually turns it off altogether. Although most developers should leave this timer on, game developers and developers whose applications do not use touch inputs can disable this timer to prevent the screen from dimming while their application is running. To disable the timer, set the idleTimerDisabled property of the shared UIApplication object to YES.
Because it results in greater power consumption, disabling the sleep timer should be avoided at all costs. The only applications that should consider using it are mapping applications, games, or applications that do not rely on touch inputs but do need to display visual content on the device’s screen. Audio applications do not need to disable the timer because audio content continues to play even after the screen dims. If you do disable the timer, be sure to reenable it as soon as possible to give the system the option to conserve more power. For additional tips on how to save power in your application, see “Reducing Power Consumption.”
When you build your iPhone application, Xcode packages it as a bundle. A bundle is a directory in the file system that groups related resources together in one place. An iPhone application bundle contains the application executable and any resources used by the application (for instance, the application icon, other images, and localized content). Table 1-2 lists the contents of a typical iPhone application bundle, which for demonstration purposes here is called MyApp). This example is for illustrative purposes only. Some of the files listed in this table may not appear in your own application bundles.
File | Description |
|---|---|
| The executable file containing your application’s code. The name of this file is the same as your application name minus the |
| The settings bundle is a file package that you use to add application preferences to the Settings application. This bundle contains property lists and other resource files to configure and display your preferences. See “Displaying Application Preferences” for more information. |
| The 57 x 57 pixel icon used to represent your application on the device home screen. This icon should not contain any glossy effects. The system adds those effects for you automatically. This file is required. For information about this image file, see “Application Icon and Launch Images.” |
| The 29 x 29 pixel icon used to represent your application in the Settings application. If your application includes a settings bundle, this icon is displayed next to your application name in the Settings application. If you do not specify this icon file, the |
| The application’s main nib file contains the default interface objects to load at application launch time. Typically, this nib file contains the application’s main window object and an instance of the application delegate object. Other interface objects are then either loaded from additional nib files or created programmatically by the application. (The name of the main nib file can be changed by assigning a different value to the |
| The 480 x 320 pixel image to display when your application is launched. The system uses this file as a temporary background until your application loads its window and user interface. For information about this image file, see “Application Icon and Launch Images.” |
| The 512 x 512 icon for an application that is distributed using ad-hoc distribution. This icon would normally be provided by the App Store; because applications distributed in an ad-hoc manner do not go through the App Store, however, it must be present in the application bundle instead. iTunes uses this icon to represent your application. (The file you specify for this property should be the same one you would have submitted to the App Store (typically a JPEG or PNG file), were you to distribute your application that way. The filename must be the one shown at left and must not include a filename extension.) |
| Also known as the information property list, this file is a property list defining key values for the application, such as bundle ID, version number, and display name. See “The Information Property List” for further information. This file is required. |
| Nonlocalized resources are placed at the top level of the bundle directory ( |
other language-specific project directories | Localized resources are placed in subdirectories with an ISO 639-1 language abbreviation for a name plus an |
An iPhone application should be internationalized and have a language.lproj directory for each language it supports. In addition to providing localized versions of your application’s custom resources, you can also localize your application icon (Icon.png), default image (Default.png), and Settings icon (Icon-Settings.png) by placing files with the same name in your language-specific project directories. Even if you provide localized versions, however, you should always include a default version of these files at the top-level of your application bundle. The default version is used in situations where a specific localization is not available.
You use the methods of the NSBundle class or the functions associated with the CFBundleRef opaque type to obtain paths to localized and nonlocalized image and sound resources stored in the application bundle. For example, to get a path to the image file sun.png (shown in “Responding to Interruptions”) and create an image file from it would require two lines of Objective-C code:
NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"sun" ofType:@"png"]; |
UIImage* sunImage = [[UIImage alloc] initWithContentsOfFile:imagePath]; |
Calling the mainBundle class method returns an object representing the application bundle. For information on loading resources, see Resource Programming Guide.
The information property list is a file named Info.plist that is included with every iPhone application project created by Xcode. It is a property list whose key-value pairs specify essential runtime-configuration information for the application. The elements of the information property list are organized in a hierarchy in which each node is an entity such as an array, dictionary, string, or other scalar type.
In Xcode, you can access the information property list by choosing Edit Active Target TargetName from the Project menu. Then in the target’s Info window, click the Properties control. Xcode displays a pane of information similar to the example in Figure 1-4.
The Properties pane shows you some, but not all, of the properties of the application bundle. When you click the “Open Info.plist as File” button, or when you select the Info.plist file in your Xcode project, Xcode displays a property list editor window similar to the one in Figure 1-5. You can use this window to edit the property values and add new key-value pairs. To see the actual key names added to the Info.plist file, Control-click the Information Property List item in the editor and select Show Raw Keys/Values from the contextual menu that appears.
Xcode automatically adds some important keys to the Info.plist file of all new projects and sets the initial value. However, there are several keys that iPhone applications use commonly to configure their launch environment and runtime behavior. The following list identifies some of the important keys that you might want to add to your application’s Info.plist file specifically for iPhone OS.
UIStatusBarStyle
UIInterfaceOrientation
UIRequiredDeviceCapabilities
UIRequiresPersistentWiFi
For detailed information about these and other properties you can include in your application’s Info.plist file, see Information Property List Key Reference.
The file for the icon displayed in the user’s Home screen has the default name of Icon.png (although the CFBundleIconFile property in the Info.plist file lets you rename it). It should be a PNG image file located in the top level of the application bundle. The application icon should be a 57 x 57 pixel image without any shine or round beveling effects. Typically, the system applies these effects to the icon before displaying it. You can override that behavior, however, by including the UIPrerenderedIcon key in your application’s Info.plist file. (For more information about the UIPrerenderedIcon key, see Information Property List Key Reference.)
iTunesArtwork. This file provides the icon that iTunes displays when distributing your application. The file for the application’s launch image is named Default.png. This image should closely resemble the application’s initial user interface; the system displays the launch image before an application is ready to display its user interface, giving users the impression of a quick launch. The launch image should also be a PNG image file, located in the top level of the application bundle. If the application is launched through a URL, the system looks for a launch image named Default-scheme.png, where scheme is the scheme of the URL. If that file is not present, it chooses Default.png instead.
To add an image file to a project in Xcode, choose Add to Project from the Project menu, locate the file in the browser, and click Add.
A nib file is a data file that stores a set of “freeze-dried” objects that the application plans to use later. Applications use nib files most often to store the windows and views that make up their user interface. When you load a nib file into your application, the nib-loading code turns the contents into real objects that your application can manipulate. In this way, nib files eliminate the need to create these same objects programmatically from your code.
Interface Builder is the visual design environment that you use to create nib files. You assemble nib files using a combination of standard objects (such as the windows and views provided by the UIKit framework) and custom objects from your Xcode projects. Creating view hierarchies within Interface Builder is a simple matter of dragging and dropping views in place. You can also configure the attributes of each object using the inspector window and create connections between objects to define their runtime relationships. All of the changes you make are subsequently saved to disk as part of your nib file.
At runtime, you load nib files into your application when you need the objects they contain. Typically, you load a nib file when your user interface changes and you need to display some new views on the screen. If your application uses view controllers, the view controller handles the nib loading process for you automatically but you can also load nib files yourself using the methods of the NSBundle class.
For information on how to design your application’s user interface, see iPhone Human Interface Guidelines. For information on how to create nib files, see Interface Builder User Guide.
This section describes the handful of tasks that every iPhone application should perform. These tasks are part of the overall life cycle of the application and are therefore part of the key ways your application integrates with iPhone OS. In the worst case, failure to handle some of these tasks could even lead to the termination of your application by the operating system.
During initialization and termination, the UIApplication class sends appropriate messages to the application delegate to let it perform any necessary tasks. Although your application is not required to respond to these messages, nearly all iPhone applications should handle them. Initialization time is where you prepare your application’s user interface and put the application into its initial running state. Similarly, termination is the time when you should be writing unsaved data and key application state to disk.
Because one iPhone application must quit before another can be launched, the time it takes to execute your initialization and termination code should be as small as possible. Initialization time is not the time to start loading large data structures that you do not intend to use right away. Your goal during startup should be to present your application’s user interface as quickly as possible, preferably in the state it was in when your application last quit. If your application requires additional time at launch to load data from the network or do other tasks that might be slow, you should get your interface up and running first and then launch the slow task on a background thread. Doing so gives you the opportunity to display a progress indicator or other feedback to the user to indicate that your application is loading the necessary data or doing something important.
Table 1-3 lists the methods of the UIApplicationDelegate protocol that you implement in your application delegate to handle initialization and termination chores. This table also lists some of the key chores you should perform in each method.
Delegate method | Description |
|---|---|
Use this method to restore the application to the state it was in during the previous session. You can also use this method to perform any custom initialization to your application data structures and user interface. | |
Use this method to save any unsaved data or key application state to disk. You can also use this method to perform additional cleanup operations, such as deleting temporary files. |
Besides the Home button, which terminates your application, the system can interrupt your application temporarily to let the user respond to important events. For example, an application can be interrupted by an incoming phone call, an SMS message, a calendar alert, or by the user pressing the Sleep button on a device. Whereas a press of the Home button terminates your application, these interruptions may be only temporary. If the user ignores the interruption, your application continues running as before. If the user decides to take a call or reply to an SMS message, however, the system does proceed to terminate your application.
Figure 1-6 shows the sequence of events that occurs during the arrival of a phone call, SMS message, or calendar alert. The steps that immediately follow describe the key points in the sequence in more detail, including some of the things your application should do in response to each event. This sequence does not reflect what happens when the user pushes the Sleep/Wake button; that sequence is described after the steps below.
The system detects an incoming phone call or SMS message, or a calendar event occurs.
The system calls your application delegate’s applicationWillResignActive: method. The system also disables the delivery of touch events to your application.
Interruptions amount to a temporary loss of control by your application. If such a loss of control might affect your application’s behavior or cause a negative user experience, you should take appropriate steps in your delegate method to prevent that from happening. For example, if your application is a game, you should pause the game. You should also disable timers, throttle back your OpenGL frame rates (if using OpenGL), and generally put your application into a sleep state. While in the resigned state, your application continues to run but should not do any significant work.
The system displays an alert panel with information about the event. The user can choose to ignore the event or respond to it.
If the user ignores the event, the system calls your application delegate’s applicationDidBecomeActive: method and resumes the delivery of touch events to your application.
You can use this delegate method to reenable timers, throttle up your OpenGL frame rates, and generally wake up your application from its sleep state. For games that are in a paused state, you should consider leaving the game in that state until the user is ready to resume play. For example, you might display an alert panel with controls to resume play.
If the user responds to the event, instead of ignoring it, the system calls your application delegate’s applicationWillTerminate: method. Your application should terminate as usual, saving any needed contextual information to return the user to the same place in your application upon your next launch.
After terminating your application, the system proceeds to launch the application responsible for the interruption.
Depending on what the user does while responding to an interruption, the system may launch your application again when that interruption ends. For example, if the user takes a phone call and then hangs up, the system relaunches your application. If, while on the call, the user goes back to the Home screen or launches another application, the system does not relaunch your application.
Important: When the user takes a call and then relaunches your application while on the call, the height of the status bar grows to reflect the fact that the user is on a call. Similarly, when the user ends the call, the status bar height shrinks back to its regular size. Your application should be prepared for these changes in the status bar height and adjust its content area accordingly. View controllers handle this behavior for you automatically. If you lay out your user interface programmatically, however, you need to take the status bar height into account when laying out your views and implement the layoutSubviews method to handle dynamic layout changes.
If the user presses the Sleep/Wake button on a device while running your application, the system calls your application delegate’s applicationWillResignActive: method, stops the delivery of touch events, and then puts the device to sleep. When the user wakes the device later, the system calls your application delegate’s applicationDidBecomeActive: method and begins delivering events to the application again. As you do with other interruptions, you should use these methods to put your application into a sleep state (or pause the game) and wake it up again. While in the sleep state, your application should use as little power as possible.
When the system dispatches a low-memory warning to your application, heed it. iPhone OS notifies the frontmost application whenever the amount of free memory dips below a safe threshold. If your application receives this warning, it must free up as much memory as it can by releasing objects it does not need or clearing out memory caches that it can easily recreate later.
UIKit provides several ways to receive low-memory warnings, including the following:
Implement the applicationDidReceiveMemoryWarning: method of your application delegate.
Override the didReceiveMemoryWarning method in your custom UIViewController subclass.
Register to receive the UIApplicationDidReceiveMemoryWarningNotification notification.
Upon receiving any of these warnings, your handler method should respond by immediately freeing up any unneeded memory. View controllers should purge any views that are currently offscreen, and your application delegate should release any data structures it can or notify other application objects to release memory they own.
If your custom objects have known purgeable resources, you can have those objects register for the UIApplicationDidReceiveMemoryWarningNotificationnotification and release their purgeable resources directly. Have these objects register if you have a few objects that manage most of your purgeable resources and it is appropriate to purge all of those resources. If you have many purgeable objects or want to coordinate the release of only a subset of those objects, however, you might want to use your application delegate to release the desired objects.
Important: Like the system applications, your applications should always handle low-memory warnings, even if they do not receive those warnings during your testing. System applications consume small amounts of memory while processing requests. When a low-memory condition is detected, the system delivers low-memory warnings to all running programs (including your application) and may terminate some background applications (if necessary) to ease memory pressure. If not enough memory is released—perhaps because your application is leaking or still consuming too much memory—the system may still terminate your application.
There are several ways to customize your basic application behavior to provide the user experience you want. The following sections describe some of the customizations that you must make at the application level.
Applications in iPhone OS normally launch in portrait mode to match the orientation of the Home screen. If you have an application that runs in both portrait and landscape modes, your application should always launch in portrait mode initially and then let its view controllers rotate the interface as needed based on the device’s orientation. If your application runs in landscape mode only, however, you must perform the following steps to make it launch in a landscape orientation initially.
In your application’s Info.plist file, add the UIInterfaceOrientation key and set its value to the landscape mode. For landscape orientations, you can set the value of this key to UIInterfaceOrientationLandscapeLeft or UIInterfaceOrientationLandscapeRight.
Lay out your views in landscape mode and make sure that their autoresizing options are set correctly.
Override your view controller’s shouldAutorotateToInterfaceOrientation: method and return YES only for the desired landscape orientation and NO for portrait orientations.
Important: The preceding steps assume your application uses view controllers to manage its view hierarchy. View controllers provide a significant amount of infrastructure for handling orientation changes as well as other complex view-related events. If your application is not using view controllers—as may be the case with games and other OpenGL ES–based applications—you are responsible for rotating the drawing surface (or adjusting your drawing commands) as needed to present your content in landscape mode.
The UIInterfaceOrientation property hints to iPhone OS that it should configure the orientation of the application status bar (if one is displayed) as well as the orientation of views managed by any view controllers at launch time. In iPhone OS 2.1 and later, view controllers respect this property and set their view’s initial orientation to match. Using this property is also equivalent to calling the setStatusBarOrientation:animated: method of UIApplication early in the execution of your applicationDidFinishLaunching: method.
UIInterfaceOrientation key. This step is not necessary in iPhone OS 2.1 and later, however. If an application handles URLs of a known type, you can use that URL scheme to communicate with the application. In most cases, however, URLs are used simply to launch another application and have it display some information that is relevant to your own application. For example, if your application manages address information, you could send a URL containing a given address to the Maps application to show that location. This level of communication creates a much more integrated environment for the user and alleviates the need for your application to implement features that exist elsewhere on the device.
Apple provides built-in support for the http, mailto, tel, and sms URL schemes. It also supports http–based URLs targeted at the Maps, YouTube, and iPod applications. Applications can register their own custom URL schemes as well. To communicate with an application, create an NSURL object with some properly formatted content and pass it to the openURL: method of the shared UIApplication object. The openURL: method launches the application that has registered to receive URLs of that type and passes it the URL. When the user subsequently quits that application, the system often relaunches your application but may not always do so. The decision to relaunch an application is made based on the user’s actions in the handler application and whether returning to your application would make sense from the user’s perspective.
The following code fragment illustrates how one application can request the services of another application (“todolist” in this example is a hypothetical custom scheme registered by an application):
NSURL *myURL = [NSURL URLWithString:@"todolist://www.acme.com?Quarterly%20Report#200806231300"]; |
[[UIApplication sharedApplication] openURL:myURL]; |
Important: If your URL type includes a scheme that is identical to one defined by Apple, the Apple-provided application is launched instead of your application. If multiple third-party applications register to handle the same URL scheme, it is undefined as to which of the applications is picked to handle URLs of that type.
If your application defines its own custom URL scheme, it should implement a handler for that scheme as described in “Implementing Custom URL Schemes.” For more information about the system-supported URL schemes, including information about how to format the URLs, see Apple URL Scheme Reference.
You can register URL types for your application that include custom URL schemes. A custom URL scheme is a mechanism through which third-party applications can interact with each other and with the system. Through a custom URL scheme, an application can make its services available to other applications.
To register a URL type for your application, you must specify the subproperties of the CFBundleURLTypes property, which was introduced in “The Information Property List.” The CFBundleURLTypes property is an array of dictionaries in the application’s Info.plist file, with each dictionary defining a URL type the application supports. Table 1-4 describes the keys and values of a CFBundleURLTypes dictionary.
Key | Value |
|---|---|
| A string that is the abstract name for the URL type. To ensure uniqueness, it is recommended that you specify a reverse-DNS style of identifier, for example, The URL-type name provided here is used as a key to a localized string in the |
| An array of URL schemes for URLs belonging to this URL type. Each scheme is a string. URLs belonging to a given URL type are characterized by their scheme components. |
Figure 1-7 shows the Info.plist file of an application being edited using the built-in Xcode editor. In this figure, the URL types entry in the left column is equivalent to the CFBundleURLTypes key you would add directly to an Info.plist file. Similarly, the “URL identifier” and “URL Schemes” entries are equivalent to the CFBundleURLName and CFBundleURLSchemes keys.
After you have registered a URL type with a custom scheme by defining the CFBundleURLTypes property, you can test the scheme in the following way:
Build, install, and run your application.
Go to the Home screen and launch Safari. (In the iPhone simulator you can go to the Home screen by selecting Hardware > Home from the menu.)
In the address bar of Safari, type a URL that uses your custom scheme.
Verify that your application launches and that the application delegate is sent a application:handleOpenURL: message.
The delegate of an application handles URL requests routed to the application by implementing the application:handleOpenURL: method. You especially need the delegate to implement this method if you have registered custom URL schemes for your application.
A URL request based on a custom scheme assumes a kind of protocol understood by those applications requesting the services of your application. The URL contains information of some kind that the scheme-registering application is expected to process or respond to in some way. Objects of the NSURL class, which are passed into the application:handleOpenURL: method, represent URLs in the Cocoa Touch framework. NSURL conforms to the RFC 1808 specification; it includes methods that return the various parts of a URL as defined by RFC 1808, including user, password, query, fragment, and parameter string. The “protocol” for your custom scheme can use these URL parts for conveying various kinds of information.
In the implementation of application:handleOpenURL: shown in Listing 1-2, the passed-in URL object conveys application-specific information in its query and fragment parts. The delegate extracts this information—in this case, the name of a to-do task and the date the task is due—and with it creates a model object of the application.
Listing 1-2 Handling a URL request based on a custom scheme
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { |
if ([[url scheme] isEqualToString:@"todolist"]) { |
ToDoItem *item = [[ToDoItem alloc] init]; |
NSString *taskName = [url query]; |
if (!taskName || ![self isValidTaskString:taskName]) { // must have a task name |
[item release]; |
return NO; |
} |
taskName = [taskName stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
item.toDoTask = taskName; |
NSString *dateString = [url fragment]; |
if (!dateString || [dateString isEqualToString:@"today"]) { |
item.dateDue = [NSDate date]; |
} else { |
if (![self isValidDateString:dateString]) { |
[item release]; |
return NO; |
} |
// format: yyyymmddhhmm (24-hour clock) |
NSString *curStr = [dateString substringWithRange:NSMakeRange(0, 4)]; |
NSInteger yeardigit = [curStr integerValue]; |
curStr = [dateString substringWithRange:NSMakeRange(4, 2)]; |
NSInteger monthdigit = [curStr integerValue]; |
curStr = [dateString substringWithRange:NSMakeRange(6, 2)]; |
NSInteger daydigit = [curStr integerValue]; |
curStr = [dateString substringWithRange:NSMakeRange(8, 2)]; |
NSInteger hourdigit = [curStr integerValue]; |
curStr = [dateString substringWithRange:NSMakeRange(10, 2)]; |
NSInteger minutedigit = [curStr integerValue]; |
NSDateComponents *dateComps = [[NSDateComponents alloc] init]; |
[dateComps setYear:yeardigit]; |
[dateComps setMonth:monthdigit]; |
[dateComps setDay:daydigit]; |
[dateComps setHour:hourdigit]; |
[dateComps setMinute:minutedigit]; |
NSCalendar *calendar = [NSCalendar currentCalendar]; |
NSDate *itemDate = [calendar dateFromComponents:dateComps]; |
if (!itemDate) { |
[dateComps release]; |
[item release]; |
return NO; |
} |
item.dateDue = itemDate; |
[dateComps release]; |
} |
[(NSMutableArray *)self.list addObject:item]; |
[item release]; |
return YES; |
} |
return NO; |
} |
Be sure to validate the input you get from URLs passed to your application; see Validating Input in Secure Coding Guide to find out how to avoid problems related to URL handling. To learn about URL schemes defined by Apple, see Apple URL Scheme Reference.
If your application uses preferences to control various aspects of its behavior, how you expose those preferences to the user depends on how integral they are to your program.
Preferences that are integral to using the application (and simple enough to implement directly) should be presented directly by your application using a custom interface.
Preferences that are not integral, or that require a relatively complex interface, should be presented using the system’s Settings application.
When determining whether a set of preferences is integral, think about the intended usage pattern. If you expect the user to make changes to preferences somewhat frequently, or if those preferences play a relatively important role in how the application behaves, they are probably integral. For example, the settings in a game are usually integral to playing the game and something the user might want to change quickly. Because the Settings application is a separate application, however, you would use it only for preferences that you do not expect the user to access frequently.
If you choose to implement preferences inside your application, it is up to you to define the interface and write the code to manage those preferences. If you choose to use the Settings application, however, your application must provide a Settings bundle to manage them.
A settings bundle is a custom resource you include in the top level of your application’s bundle directory. An opaque directory with the name Settings.bundle, the settings bundle contains specially formatted data files (and supporting resources) that tell the Settings application how to display your preferences. These files also tell the Settings application where to store the resulting values in the preferences database, which your application then accesses using the NSUserDefaults or CFPreferences APIs.
If you implement your preferences using a settings bundle, you should also provide a custom icon for your preferences. The Settings application looks for an image file with the name Icon-Settings.png at the top level of your application bundle and displays that image next to your application name. The image file should be a 29 x 29 pixel PNG image file. If you do not provide this file at the top level of your application bundle, the Settings application uses a scaled version of your application icon (Icon.png) by default.
For more information about creating a Settings bundle for your application, see “Application Preferences.”
If an iPhone OS–based device does not receive touch events for a specified period of time, it turns off the screen and disables the touch sensor. Locking the screen in this way is an important way to save power. As a result, you should leave this feature enabled except when absolutely necessary to prevent unintended behavior in your application. For example, you might disable screen locking if your application does not receive screen events regularly but instead uses other features (such as the accelerometers) for input.
To disable screen locking, set the idleTimerDisabled property of the shared UIApplication object to YES. Be sure to reset this property to NO at times when your application does not need to prevent screen locking. For example, you might disable screen locking while the user is playing a game but should reenable it when the user is in setup screens or is otherwise not actively playing the game.
Ideally, the text, images, and other content that iPhone applications display to users should be localized for multiple languages. The text that an alert dialog displays, for example, should be in the preferred language of the user. The process of preparing a project for content localized for particular languages is called internationalization. Project components that are candidates for localization include:
Code-generated text, including locale-specific aspects of date, time, and number formatting
Static text—for example, an HTML file loaded into a web view for displaying application help
Icons (including your application icon) and other images when those images either contain text or have some culture-specific meaning
Sound files containing spoken language
Using the Settings application, users select the language they want to see in their applications’ user interfaces from the Language preferences view (see Figure 1-8). They get to this view from the International group of settings, accessed from General settings.
The chosen language is associated with a subdirectory of the bundle. The subdirectory name has two components: an ISO 639-1 language code and a .lproj suffix. For example, to designate resources localized to English, the bundle would be named en.lproj. By convention, these language-localized subdirectories are called lproj directories.
lproj directories. Although region information is used for formatting dates, numbers, and other types of information, it is not taken into consideration when choosing which language directory to use. For more information about language and region codes, see “Language and Locale Designations” in Internationalization Programming Topics. An lproj directory contains all the localized content for a given language. You use the facilities of the NSBundle class or the CFBundleRef opaque type to locate (in one of the application’s lproj directories) resources localized for the currently selected language. Listing 1-3 gives an example of such a directory containing content localized for English (en).
Listing 1-3 The contents of a language-localized subdirectory
en.lproj/ |
InfoPlist.strings |
Localizable.strings |
sign.png |
This subdirectory example has the following items:
The InfoPlist.strings file contains strings assigned as localized values of certain properties in the project’s Info.plist file (such as CFBundleDisplayName). For example, the CFBundleDisplayName key for an application named Battleship in the English version would have this entry in InfoPlist.strings in the fr.lproj subdirectory:
CFBundleDisplayName = "Cuirassé"; |
The Localizable.strings file contains localized versions of strings generated by application code.
The sign.png file in this example is a file containing a localized image.
To internationalize strings in your code for localization, use the NSLocalizedString macro in place of the string. This macro has the following declaration:
NSString *NSLocalizedString(NSString *key, NSString *comment); |
The first parameter is a unique key to a localized string in a Localizable.strings file in a given lproj directory. The second parameter is a comment that indicates how the string is used and therefore provides additional context to the translator. For example, suppose you are setting the content of a label (UILabel object) in your user interface. The following code would internationalize the label’s text:
label.text = NSLocalizedString(@"City", @"Label for City text field"); |
You can then create a Localizable.strings file for a given language and add it to the proper lproj directory. For the above key, this file would have an entry similar to the following:
"City" = "Ville"; |
NSLocalizedString calls in your code where appropriate and then run the genstrings command-line tool. This tool generates a Localizable.strings template that includes the key and comment for each string requiring translation. For further information about genstrings, see the genstrings(1) man page. For more information about internationalization and how you support it in your iPhone applications, see Internationalization Programming Topics.
At each step in the development of your application, you should consider the implications of your design choices on the overall performance of your application. The operating environment for iPhone applications is more constrained because of the mobile nature of iPhone and iPod touch devices. The following sections describe the factors you should consider throughout the development process.
You should be careful what work you perform from the main thread of your application. The main thread is where your application handles touch events and other user input. To ensure your application is always responsive to the user, you should never use the main thread to perform long-running tasks or to perform tasks with a potentially unbounded end, such as tasks that access the network. Instead, you should always move those tasks onto background threads. The preferred way to do so is to wrap each task in an operation object and add it to an operation queue, but you can also create explicit threads yourself.
Moving tasks into the background leaves your main thread free to continue processing user input, which is especially important when your application is starting up or quitting. During these times, your application is expected to respond to events in a timely manner. If your application’s main thread is blocked at launch time, the system could kill the application before it even finishes launching. If the main thread is blocked at quitting time, the system could kill the application before it has a chance to write out crucial user data.
For more information about using operation objects and threads, see Concurrency Programming Guide.
Because the iPhone OS virtual memory model does not include disk swap space, applications are somewhat more limited in the amount of memory they have available for use. Using large amounts of memory can seriously degrade system performance and potentially cause the system to terminate your application. When you design, therefore, make it a high priority to reduce the amount of memory used by your application.
There is a direct correlation between the amount of free memory available and the relative performance of your application. Less free memory means that the system is more likely to have trouble fulfilling future memory requests. If that happens, the system can always remove code pages and other nonvolatile resources from memory. However, removing those resources may only be a temporary fix, especially when those resources are needed again a short time later. Instead, minimize your memory use in the first place, and clean up the memory you do use in a timely manner.
The following sections provide more guidance on how to use memory efficiently and how to respond when there is only a small amount of available memory.
Table 1-5 lists some tips on how to reduce your application’s overall memory footprint. Starting off with a low footprint gives you more room for the data you need to manipulate.
Tip | Actions to take |
|---|---|
Eliminate memory leaks. | Because memory is a critical resource in iPhone OS, your application should not have any memory leaks. Allowing leaks to exist means your application may not have the memory it needs later. You can use the Instruments application to track down leaks in your code, both in the simulator and on actual devices. For more information on using Instruments, see Instruments User Guide. |
Make resource files as small as possible. | Files reside on the disk but must be loaded into memory before they can be used. Property list files and images are two resource types where you can save space with some very simple actions. To reduce the space used by property list files, write those files out in a binary format using the |
Use Core Data or SQLite for large data sets. | If your application manipulates large amounts of structured data, store it in a Core Data persistent store or in a SQLite database instead of in a flat file. Both Core Data and SQLite provides efficient ways to manage large data sets without requiring the entire set to be in memory all at once. The Core Data feature was introduced in iPhone OS 3.0. |
Load resources lazily. | You should never load a resource file until it is actually needed. Prefetching resource files may seem like a way to save time, but this practice actually slows down your application right away. In addition, if you end up not using the resource, loading it simply wastes memory. |
Build your program using Thumb. | Adding the |
iPhone applications use a managed memory model, whereby you must explicitly retain and release objects. Table 1-6 lists tips for allocating memory inside your program.
Tip | Actions to take |
|---|---|
Reduce your use of autoreleased objects. | Objects released using the |
Impose size limits on resources. | Avoid loading large resource files when a smaller one will do. Instead of using a high-resolution image, use one that is appropriately sized for iPhone OS–based devices. If you must use large resource files, find ways to load only the portion of the file that you need at any given time. For example, rather than load the entire file into memory, use the |
Avoid unbounded problem sets. | Unbounded problem sets might require an arbitrarily large amount of data to compute. If the set requires more memory than is available, your application may be unable to complete the calculations. Your applications should avoid such sets whenever possible and work on problems with known memory limits. |
For detailed information on how to allocate memory in iPhone applications, and for more information on autorelease pools, see Cocoa Objects in Cocoa Fundamentals Guide.
The processors found in iPhone–OS based devices are capable of performing floating-point calculations in hardware. If you have an existing program that performs calculations using a software-based fixed-point math library, you should consider modifying your code to use floating-point math instead. Hardware-based floating-point computations are typically much faster than their software-based fixed-point equivalents.
Important: Of course, if your code does use floating-point math extensively, remember to compile that code without the -mthumb compiler option. The Thumb option can reduce the size of code modules but it can also degrade the performance of floating-point code.
Power consumption on mobile devices is always an issue. The power management system in iPhone OS conserves power by shutting down any hardware features that are not currently being used. In addition to avoiding CPU-intensive operations or operations that involve high graphics frame rates, you can help improve battery life by optimizing your use of the following features:
The CPU
Wi-Fi and baseband (EDGE, 3G) radios
The Core Location framework
The accelerometers
The disk
The goal of your optimizations should be to do the most work you can in the most efficient way possible. You should always optimize your application’s algorithms using Instruments and Shark. But it is important to remember that even the most optimized algorithm can still have a negative impact on a device’s battery life. You should therefore consider the following guidelines when writing your code:
Avoid doing work that requires polling. Polling prevents the CPU from going to sleep. Instead of polling, use the NSRunLoop or NSTimer classes to schedule work as needed.
Leave the idleTimerDisabled property of the shared UIApplication object set to NO whenever possible. The idle timer turns off the device’s screen after a specified period of inactivity. If your application does not need the screen to stay on, let the system turn it off. If your application experiences side effects as a result of the screen turning off, you should modify your code to eliminate the side effects rather than disable the idle timer unnecessarily.
Coalesce work whenever possible to maximize idle time. It takes more power to do work periodically over an extended period of time than it does to perform the same amount of work all at once. Performing work periodically prevents the system from powering down hardware over a longer period of time.
Avoid over-accessing the disk. For example, if your application saves state information to the disk, do so only when that state information changes and coalesce changes whenever possible to avoid writing small changes at frequent intervals.
Do not draw to the screen faster than needed. Drawing is an expensive operation when it comes to power. Do not rely on the hardware to throttle your frame rates. Draw only as many frames as your application actually needs.
If you use the UIAccelerometer class to receive regular accelerometer events, disable the delivery of those events when you do not need them. Similarly, set the frequency of event delivery to the smallest value that is suitable for your needs. For more information, see “Accessing Accelerometer Events.”
The more data you transmit to the network, the more power must be used to run the radios. In fact, accessing the network is the most power-hungry operation you can perform and should be minimized by following these guidelines:
Connect to external network servers only when needed, and do not poll those servers.
When you must connect to the network, transmit the smallest amount of data needed to do the job. Use compact data formats and do not include excess content that will simply be ignored.
Transmit data in bursts rather than spreading out transmission packets over time. The system turns off the Wi-Fi and cell radios when it detects a lack of activity. When it transmits data over a longer period of time, your application uses much more power than when it transmits the same amount of data in a shorter amount of time.
Connect to the network using the Wi-Fi radios whenever possible. Wi-Fi uses less power and is preferred over the baseband radios.
If you use the Core Location framework to gather location data, disable location updates as soon as you can and set the distance filter and accuracy levels to appropriate values. Core Location uses the available GPS, cell, and Wi-Fi networks to determine the user’s location. Although Core Location works hard to minimize the use of these radios, setting the accuracy and filter values gives Core Location the option to turn off hardware altogether in situations where it is not needed. For more information, see “Getting the User’s Current Location.”
iPhone OS comes with several applications for tuning the performance of your application. Most of these tools run on Mac OS X and are suitable for tuning some aspects of your code while it runs in the simulator. For example, you can use the simulator to eliminate memory leaks and make sure your overall memory usage is as low as possible. You can also remove any computational hotspots in your code that might be caused by an inefficient algorithm or a previously unknown bottleneck.
After you have tuned your code in the simulator, you should then use the Instruments application to further tune your code on a device. Running your code on an actual device is the only way to tune your code fully. Because the simulator runs in Mac OS X, it has the advantage of a faster CPU and more usable memory, so its performance is generally much better than the performance on an actual device. And using Instruments to trace your code on an actual device may point out additional performance bottlenecks that need tuning.
For more information on using Instruments, see Instruments User Guide.
Last updated: 2009-10-19