Chapter 4. Applications on OS X and iOS

As far as users are concerned, applications are the only thing on their computers besides their files. After all, a computer is defined by what it can do for the user, and what it can do is defined by the applications that are installed.

As a developer, it’s easy to get drawn into the details of how an app is put together—the individual classes, methods, and structures. However, the application as a whole is what’s sold to the user, and that’s all users care about.

In this chapter, you’ll learn how applications are structured on OS X and iOS, how they differ from other distributable code, what they can do on the system, and what they’re prevented from doing by the built-in security measures provided by the OS.

What Is an Application?

Applications on iOS and OS X are packaged differently from applications on other platforms, most notably Windows. On other platforms, the end result of compiling your project is a binary file that contains the compiled code. It’s then up to you as a developer to package that binary file up with the resources it needs. On Linux, you generate a package file (which can vary depending on the distribution you’re using), and on Windows, it’s traditional to create an “installer,” which is an additional application that unpacks the binary and resources.

OS X and iOS take a different approach to applications. This approach stems from the concept of a “package”—a folder that contains a number of items but is presented to the user as a single file. Many document formats use packages as a convenient way to store and organize their data, since storing different chunks of data as separate files means that the program doesn’t have to implement logic that unpacks a single file.

Note

If you’re coming from a Linux background, note that “package,” in this context, means something different. A package file is just a folder that’s presented as a single file, while on Linux “package” means a redistributable file used to install software. OS X also uses the word “package” in this way—you can generate .pkg files that contain software, which when opened install the software onto your machine. When you upload an app to the Mac App Store, for example, you upload a package.

And just to add to the confusion, Cocoa doesn’t call these files packages, but rather calls them “bundles.”

Applications, therefore, are actually folders that contain the compiled binary, plus any resources they may need. The structure of applications differs slightly between OS X and iOS, but the fundamental philosophy of how an application is packaged remains the same. You can take a look inside an application by right-clicking one in the Finder and choosing Show Package Contents.

When you compile a project in Xcode and generate an application, Xcode creates the application package, and copies in any resources needed. If you’re creating a Mac application, you can then just zip it up and send it to anyone for them to run it. On iOS it’s a little different, because apps must be code-signed and provisioned before being run on the device.

One advantage to this is that applications are entirely self-contained and can be moved anywhere on a Mac.

Note

Because applications can be moved, it used to be commonplace to add code to an application that detected if the app was not in the Applications folder and offered to move itself there to keep the user’s Downloads folder tidy.

This is less common in the days of the App Store, which installs all applications directly into the Applications folder. However, if your application is being distributed by means other than the Mac App Store, it’s worthwhile to include this logic anyway.

Applications, Frameworks, Utilities, and More

Applications aren’t the only products that you can produce from Xcode. You can also generate frameworks, which are loadable bundles of code and resources that other applications (including your own) can use. Frameworks are actually very similar to applications in structure—they contain a binary file and any resources—but they’re not standalone and are designed to be used by other apps.

One prime example of a framework is AppKit.framework, which is used by every Mac application. On iOS, the equivalent framework is UIKit.framework.

Note

“Cocoa” is the term used by Apple to refer to the collection of libraries used by applications on OS X. On iOS, the equivalent term is “Cocoa Touch,” as it’s adapted for touch-screen devices.

What Are Apps Composed Of?

In order to function as an application on iOS or OS X, an application must have two things at a minimum:

  • The compiled binary

  • An information file describing the app to the system

The compiled binary is simply the end result of Xcode compiling all of your Objective-C source code and linking it together.

Information describing the app to the system is saved in a file called Info.plist. Among other things, Info.plist contains:

  • The name of the application’s icon file

  • What kinds of documents the application can open

  • The name of the compiled binary

  • The name of the interface file to load when the application starts up

  • What languages the application supports (such as French, English, and so on)

  • Whether the application supports multitasking (for iOS apps)

  • The Mac App Store category the application is in (for OS X apps)

Info.plist is really important—in fact, if you remove it from the application bundle, the app can’t launch.

Applications also contain every resource that was compiled in—all the images, files, sounds, and other items that were added to the project via Xcode. The application is able to refer to these resources at runtime.

You can take a look at the structure of an OS X application by following these steps:

  1. Open Xcode, and create a new OS X application. Don’t bother changing any settings when Xcode asks—just name the app whatever you like and save it somewhere.

  2. Build the application. Press ⌘-B, or choose Product→Build.

  3. Open the Products group in the project navigator. It will now contain the .app, which is the end result of the build process. Right-click it and choose Show in Finder. The Finder will open, revealing where Xcode put the app.

  4. Right-click the application and choose Show Package Contents. The Finder will show the contents of the bundle.

The structures of OS X and iOS application bundles are different. On iOS, everything is contained at the root of the package’s folder; on OS X, the structure is more rigorous.

The structure of a Mac application named MyApp looks like this:

MyApp.app

The top level of the package.

Contents

A folder that contains the application itself.

Info.plist

The file that describes the application to the system.

MacOS

A folder that contains the app’s compiled binary.

MyApp

The app’s compiled binary.

PkgInfo

A file included for legacy reasons that describes the app’s maker and what the app is.

Resources

A folder that contains all of the compiled-in resources.

The structure of an iOS application named MyApp looks like this:

MyApp

The app’s compiled binary.

Info.plist

The file that describes the application to the system.

Default.png

The image that is shown while the app is launching.

Default@2x.png

The high-resolution version of Default.png.

embedded.mobileprovision

The provisioning profile that identifies the app as able to run on a device.

Entitlements.plist

A file that describes what the application may or may not do.

Because your application could be anywhere on the system, your code can’t use absolute paths to determine the location of resources. Thankfully, Cocoa already knows all about packages and how to work with them.

Using NSBundle to Find Resources in Applications

As far as your code goes, your application works the same regardless of which platform it’s running on, thanks to a useful class called NSBundle. This class allows your code to know where it is on the disk and how to get at the compiled resources.

This is especially important for iOS applications, since these apps are placed in arbitrary folders by the OS when they’re installed. This means that your code cannot depend upon being in a single place, and you can’t hardcode paths. Of course, doing that is a bad idea anyway, but on iOS, it’s guaranteed to cause failures.

You can use NSBundle to determine the location of the application’s package on disk, but most of the time you only need to know about the location of the individual resources.

NSBundle allows you to determine both URLs and plain file paths for resources on the disk. All you need to know is the name and type of the resource.

For example, the following code returns an NSString that contains the absolute path for a resource called SomeImage.png:

NSString* resourcePath = [[NSBundle mainBundle] pathForResource:@"SomeImage"
     ofType:@"png"];
// resourcePath is now a string containing the
// absolute path reference to SomeImage.png

Note that call to [NSBundle mainBundle]—it’s possible to have more than one bundle around. (Remember, Cocoa refers to packages—that is, folders containing app resources—as bundles.)

You can also get URLs to resources as well:

NSURL* resourceURL = [[NSBundle mainBundle] URLForResource:@"SomeImage" 
                                                    ofType:@"png"];

This method looks inside the Resources folder in the application bundle for the named file. (On iOS, it looks inside the root folder of the application bundle.)

Absolute paths and URLs are functionally the same when referring to files stored on disk, but using URLs is preferred—a string could theoretically contain anything, whereas a URL always points to a location. This includes file URLs, which look like this: file:///Applications/Xcode.app/. You can therefore use URLs in any case where you’d normally use a file path.

If you add an image or other resource to your project, it is copied into the application bundle when the project is built. For Mac apps, the resources are copied into the Resources folder, and for iOS apps, the resources are copied into the root folder of the application.

The Application Lifecycle

Every program starts, runs, and quits. What’s interesting is what it does in between. For the most part, applications on OS X and iOS behave similarly, with the exception that iOS handles multitasking in a different way from standard desktop applications.

In this section, we’ll walk through the lifecycle of both kinds of applications, and discuss what happens at various stages of an app’s life.

OS X Applications

When an application is launched, the first thing the system does is open the application’s Info.plist. From this file, the system determines where the compiled binary is located, and launches it. From this point on, the code that you write is in control.

In addition to the compiled code, applications almost always have a collection of objects that were prepared at design time and bundled with the application. These are usually interface objects—preprepared windows, controls, and screens—which are stored inside a nib file when the application is built. When the application runs, these nib files are opened, and the premade objects are loaded into memory.

Note

For more information on nib files and how they’re built, see Constructing an Interface.

The first thing an application does is open the nib file and deserialize its contents. This means that the application unpacks the windows, controls, and anything else stored in it and links them together. The main nib also contains the application delegate object, which is unpacked with all the rest.

When an object is unpacked from a nib, it is sent the awakeFromNib message. This is the moment at which that object can begin to run code.

Note

Objects that are unpacked from a nib are not sent an init message because they were already initialized when the developer dragged and dropped them into the interface. When working with nib files, it’s important to understand that when you add an object to a nib file, that object is created at that moment, and “freeze-dried” when the nib file is saved. When the nib file is opened, the object is “rehydrated” and gets back to work. After the object is rehydrated, it is sent the awakeFromNib message to let it know that it’s awake.

To summarize: objects that are loaded from a nib receive the awakeFromNib message. Objects that are created by your code receive the init method.

At this point, the application is ready to start running properly. The first thing it does is to send the application delegate the applicationDidFinishLaunching: method. After that method completes, the application enters the run loop.

The run loop is an infinite loop, managed by Cocoa that continues looping until the application quits. The purpose of the run loop is to listen for events—keyboard input, mouse movement and clicks, timers going off, etc.—and send those events to the relevant destinations. For example, say you have a button hooked up to a method that should be run when the button is clicked. When the user clicks the button, the mouse-click event is sent to the button, which then causes its target method to get run.

On OS X, applications continue to run when the user selects another app. When the user changes applications, the application delegate receives the applicationWillResignActive: message, indicating that the application is about to stop being the active one. Soon after, the app delegate receives the applicationDidResignActive: method.

The reason these two methods are separate is to let your code manage what happens to the screen’s contents when the home button is tapped on iOS, or when the user switches to another app on OS X. When applicationWillResignActive: is called, your application is still present on the screen. When the application is no longer visible, the application delegate receives applicationDidResignActive:.

When the user comes back to the app, the application delegate receives a pair of similar methods: applicationWillBecomeActive: and applicationDidBecomeActive:. These are sent immediately before and after the application returns to being the active one.

The event loop is terminated when the application quits. When this happens, the application delegate receives the applicationWillTerminate: message, which is sent immediately before the app quits. This is the last opportunity an app has to save files before quitting.

iOS Applications

iOS applications behave in a broadly similar manner to OS X applications, with a few differences. The main one is that iOS applications are presented differently from desktop apps, and the tighter memory constraints on an iOS device mean that there are more stringent rules about multitasking.

On iOS, only one application is on the screen at a time—any other applications are completely hidden. The visible application is known as the foreground application, and any apps also running are background applications. There are strict limits on how long an application may run in the background, which we’ll discuss shortly.

When using an application on iOS, a user may be interrupted by something else—an incoming phone call, for example, which replaces the app with which the user was interacting. The application is still technically considered to be in the foreground, but it is now inactive. If the user accepts the phone call, the phone application becomes the foreground application, and the previous app moves to the background.

There are other methods by which an application can become inactive, such as when the user pulls down the notifications tray (by swiping down from the top of the screen), opens the task switcher (by double-tapping on the home button), or performs some other action. When an application becomes inactive, it’s a signal that it may be exited, so your app should make sure to save any work.

The iOS application lifecycle is almost identical to that of an OS X application. When the app is launched, the Info.plist file is checked, the compiled binary is found and loaded, and the application begins running code, starting by unpacking the contents of the main nib.

When the application completes loading, the application delegate receives the applicationDidFinishLaunching:withOptions: method. This is similar to the OS X counterpart, but adds an additional parameter—a dictionary, which contains information about why and how the application was launched.

Applications are most commonly launched directly by the user by tapping on the icon. They can also be launched by other applications, such as when an app passes a file to another. The options dictionary contains information that describes the circumstances under which the application launched.

Just as with OS X applications, iOS applications also receive applicationWillResignActive: and applicationDidBecomeActive: methods (with one difference—on OS X, the parameter to these methods is an NSNotification object, whereas on iOS the parameter is a UIApplication).

When an application is quit by the user on OS X, we have seen that the application delegate receives the applicationWillTerminate: method. This was also the case for iOS applications, until iOS 4. At this point, multitasking was introduced, and the lifecycle of iOS applications changed.

Multitasking on iOS

Applications on iOS are permitted to run in the background, but only under certain very limited conditions. That’s because iOS devices are much more constrained than OS X devices in the areas of CPU power, memory space, and battery capacity. A MacBook Pro is expected to run for around 7 hours on battery, with a full set of applications loaded and running—word processor, web browser, and so on. An iPhone 4S, by contrast, is expected to last for 8 hours on WiFi while browsing the Internet—on a battery with a fraction of the capacity of a full-size laptop battery. Additionally, a MacBook Pro (at the time of writing) ships with 8 GB of memory, while an iPhone 5S has only 1 GB.

There’s simply no room to fit all the applications at once, so iOS is forced to make some decisions about what applications can run in the background and for how long.

When an application exits (for example, when the user hits the home button or another application launches), the application is suspended—it hasn’t quit, but it stops executing code and its memory is locked. When the application resumes, it simply picks up where it left off.

This means that the application remains in memory, but stops consuming the system’s power-draining resources such as the CPU and location hardware. However, memory is still tight on the iPhone, so if another app needs more memory, the application is simply terminated without notice.

Note that an application that is suspended doesn’t get to run any code, and therefore can’t get notified that it’s being terminated while suspended. This means that any critical data must be saved when the application delegate is told that the application is being moved to the background.

Applications are not told when they are suspended or when they are woken up. They are told when they move into and out of the background, however, through the following delegate methods:

- (void)applicationDidEnterBackground:(UIApplication *)application;
- (void)applicationWillEnterForeground:(UIApplication *)application;

applicationDidEnterBackground: is called immediately after the application has moved to the background state. The application will be suspended after the method has run, which means that the app needs to save any data it’s working on because it may be terminated while suspended.

applicationWillEnterForeground: is called just before the application comes back on screen, and is your application’s opportunity to get set up to work again.

As mentioned above, applications that are suspended are candidates for termination if the new foreground app needs more memory. As an application developer, you can reduce the chances of this happening by reducing the amount of memory your application is using—by freeing large objects, unloading images, and so on.

Note

If you are able, try to reduce the amount of memory being used to under 16 MB. When the application is suspended and the memory usage is under 16 MB, the system will store the application’s memory on the flash chips and remove it from memory entirely. When the application is resumed, the application’s memory state is reloaded from the stored memory on the flash chips—meaning that the application won’t be evicted from memory due to another application’s memory demands. We’ll look at how to measure memory usage in Chapter 16.

An application can request to run in the background for a short period of time. This background period can be no longer than 10 minutes, and it exists to allow your application to complete a long-running process—writing large files to disk, completing a download, or some other lengthy process. At the end of the 10 minutes, your application must indicate to the OS that it is done or it will be terminated (not suspended, but terminated—gone from memory completely).

To run tasks in the background, you need to add code that looks like this to your application delegate:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{
        // Stop performing the task in the background (stop calculations, etc)
        // This expiration handler block is optional, but recommended!

        // Then, tell the system that the task is complete.
        [application endBackgroundTask:backgroundTask];
        backgroundTask = UIBackgroundTaskInvalid;
    }];

    // Start running a block in the background to do the work.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
                   ^{
        // Start doing the background work: write, calculate, etc.

        // Once the work is done, tell the system
        // that the task is complete.
        [application endBackgroundTask:backgroundTask];
        backgroundTask = UIBackgroundTaskInvalid;
    });
}

In iOS 7, there is no guarantee that the extra time to perform background tasks will be in one contiguous chunk; the time may be broken up into multiple chunks to improve battery life. Also exclusive to iOS 7 are two new means of running tasks in the background: background fetching and background notifications.

Background fetching is designed for applications that require periodic updates, such as weather applications or social network applications like Twitter. With background fetching enabled, an application can be woken up in the background to retrieve up-to-date information in the background to have ready to immediately display when the user brings the application to the foreground.

To use background fetching, there are a few things you need to do:

  • Select the project in the Project Navigator, open the Capabilities tab, and enable Background Fetch from the Background Modes section.

  • In your code, you need to call setMinimumBackgroundFetchInterval: to let iOS know approximately how often to wake your application so it can fetch updates. If you do not set a minimum interval, iOS will default to never waking your application for performing fetches.

To actually perform the fetching when iOS wakes your application, you will have to add code to your application delegate that looks like this:

- (void)application:(UIApplication *)application 
performFetchWithCompletionHandler:
(void (^)(UIBackgroundFetchResult))completionHandler
{
    // check for new data to fetch
    // then tell the system you are finished
    // what you tell the system changes if you found new data or not
    
    // newData is a BOOL representing here if there was new data fetched or not
    if (newData)
        completionHandler(UIBackgroundFetchResultNewData);
    else
        completionHandler(UIBackgroundFetchResultNoData);
}
        

Background notifications allow your application to receive notifications and process them in the background. Background notifications could be used in an instant messaging application to automatically update the conversation while the application is in the background or to alert your application when new content is available to be fetched.

Background notifications operate in a manner very similar to background fetching, and require a similar setup before being available for use in your application. Your application will need to be able to handle Notifications, which are discussed in Chapter 17, and your application will need to enable Remote notifications.

To enable remote notifications, select the project in the project navigator, open the capabilities tab, and enable Remote notifications from the "Background Modes section.

Much like background fetch, an application method that handles the notifications is called whenever your application receives a notification. The code to receive this notifications looks this:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:
(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult 
result))handler
        

This method functions in a manner very similar to the method for handling background fetching and even requires the same results to be passed into the callback handler when completed. The main difference is the userInfo parameter, which is a dictionary containing the data that the remote notification contained.

Warning

Keep in mind that despite letting you set a minimum interval for fetching in the background, iOS will wake your application when it determines is the best time without causing unnecessary drain on the device’s battery. In a similar manner, Apple will limit how many remote notifications are sent to the device for the same reasons. If your application isn’t behaving exactly as you set it, this might be the cause.

There are other cases in which an application can run in the background for longer periods of time, all of which are geared toward more specialized applications:

  • Applications that play audio in the background can remain active for as long as they like, until the user starts playing audio from another app. For example, the Pandora Internet radio app can run in the background until the user starts playing music from the Music application.

  • Applications that track the user’s location can run for as long as they like.

  • Voice over IP (VoIP) applications like Skype are allowed to run periodically to check in with their server, but aren’t allowed to run indefinitely except when a call is active.

In summary, if you’re writing an application for iOS, you can only expect to be running on the device when the user is directly accessing your app. When the user can’t see your application, it quite literally becomes a case of “out of sight, out of mind.”

The Application Sandbox

OS X and iOS implement a number of features to improve the overall level of security for the user. One of these features is the application sandbox, a tool that restricts what an application is allowed to do. The application exists inside the sandbox, and may not try to access any system resources (hardware, user data, and so on) that is outside the sandbox.

Sandboxes are somewhat optional for Mac applications, and mandatory for iOS applications.

A sandbox improves the security of the system by preventing an app from doing something that either Apple or the user does not want it to do. This is specifically useful for improving the security of apps, because the majority of hacks take the form of exploiting a bug in an existing application. Adobe’s Acrobat Reader and Microsoft’s Internet Explorer 6 are two applications through which malicious people have been able to compromise other users’ systems (install extra software, retrieve private data, and so on). These exploits take the form of modifying the compromised application to make it perform the intruder’s bidding.

Sandboxes solve this problem by preventing (at a kernel level) an application from accessing user data, communicating with the network, accessing hardware like the camera and microphone, and so on. Even if the software has an exploitable bug, the intruder cannot access user data because the application is not permitted to reach outside of its sandbox.

Applications that are downloaded from the iOS App Store are automatically placed in a sandbox; we will discuss this in more detail in Application Restrictions. Applications that are distributed via the Mac App Store require being sandboxed as well; however, apps that you distribute yourself do not.

For more information on how the sandbox affects you as a developer, see Working with the Sandbox in Chapter 9.

Application Restrictions

As mentioned above, a sandbox restricts what an application can do. The restrictions vary significantly between iOS and OS X, because applications on OS X have traditionally been less restricted in terms of what they’re allowed to do.

For example, a Mac application can request read/write access to any of the user’s files. An iOS application can only work with its own documents, and can’t open any files outside of it.

iOS application restrictions

When an iOS app is installed on the device, it’s placed in a folder that has a structure like that shown in Figure 4-1.

iOS application structure
Figure 4-1. iOS application structure

This folder contains the following items:

Documents

Stores all documents belonging to the application

Library

Stores all settings and configuration info

Caches

Contains data that is useful to have on disk, but could be regenerated; items in this folder are deleted by the system if it needs to free some space

Preferences

Stores settings and preferences

tmp

Stores files temporarily; items in this folder are periodically deleted by the system

Application.app

The application package

An iOS application is not allowed to work with any file outside of its folder. This prevents the bundle from reading private information (like phone call logs) or modifying any system files.

This restriction on accessing files outside the folder is the only significant restriction imposed on iOS apps. Mac applications have a much more fine-grained set of restrictions.

Mac application restrictions

The idea of putting restrictions on what Mac apps can do only arrived with the release of the Mac App Store, which means that Apple had quite a bit of time to decide how to implement it.

When you decide to make your application sandboxed, Xcode presents you with a number of options that determine what your application is allowed to do. These options are called entitlements.

The available entitlements that your Mac application can request are:

Filesystem

You can determine whether the application has read/write, read-only, or no access to the filesystem. You can also control whether the application can work with the Downloads folder.

Network

You can determine whether the application is allowed to make outgoing connections and accept incoming connections.

Hardware

You can determine whether the application is allowed to access the built-in camera and microphone, communicate with devices via USB, and print.

App communication

You can determine whether the application is allowed to work with the data managed by the Address Book or Calendar, and whether it is allowed to work with the user’s location information.

Music, movies, and pictures folder access

You can determine whether the application can work with the user’s music, photos, and movies by controlling whether the app has read/write, read-only, or no access to these folders. You can set each folder’s access permissions separately.

Private APIs

One of the rules that Apple imposes on applications that are sold via the iTunes App Store or the Mac App Store is that apps are only allowed to communicate with the system via the classes and methods that Apple has documented and indicated are for developer use.

There are many “private” classes and methods that Apple uses behind the scenes. For example, the code that determines whether an iOS device is locked with a passcode is undocumented; Apple uses it (such as in the Find My Friends app), but developers like us may not.

Apple scans all submitted applications as part of the App Store review process. This happens automatically, before a human being sits down to review your application. If your application is rejected for using a private API, you must remove the private API usage and resubmit. If Apple notices that your app uses private APIs after the app has gone live in the App Store, they’ll simply take down the app.

The point is clear: Apple does not like developers using undocumented APIs. This is because documented APIs are known by Apple to be safe and (mostly) bug-free. Documented APIs are also features that Apple has committed to, and won’t change underneath you. Undocumented APIs, on the other hand, are often still under active development, or may provide access to parts of the OS that Apple considers out-of-bounds for app developers.

Get Learning Cocoa with Objective-C, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.