Tải bản đầy đủ
Chapter 3. Applications on OS X and iOS

Chapter 3. Applications on OS X and iOS

Tải bản đầy đủ

If you’re coming from a Linux background, note that “package,” in
this context, means something different. A package file is just a fold‐
er 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 soft‐
ware, 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, the Cocoa framework doesn’t call
folders that are presented as single 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 necessary resources. 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.
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 Appli‐
cations 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 ap‐
plications (including your own) can use. Frameworks are actually very similar to ap‐
plications in structure—they contain a binary file and any resources—but they’re not
standalone and are designed to be used by other apps.

64

| Chapter 3: Applications on OS X and iOS

One prime example of a framework is AppKit.framework, which is used by every Mac
application. On iOS, the equivalent framework is UIKit.framework.
“Cocoa” is the term used by Apple to refer to the collection of libra‐
ries used by applications on OS X. On iOS, the equivalent term is
“Cocoa Touch,” as it’s adapted for touchscreen 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 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, etc.)
• 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 can
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.
What Is an Application?

|

65

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, but 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 legacy file 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 double-resolution version of Default.png, used on certain high-resolution de‐
vices (e.g., the iPhone 6 or the iPad Air)

66

| Chapter 3: Applications on OS X and iOS

Default@3x.png
The triple-resolution version of Default.png, used on higher resolution devices (e.g.,
the iPhone 6 Plus)
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, as 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 SomeFile.txt:
let resourcePath = NSBundle.mainBundle()
.pathForResource("SomeFile", ofType: "txt")
// resourcePath is now a string containing the
// absolute path reference to SomeFile.txt, or nil

Note that this code snippet calls NSBundle.mainBundle()—it’s possible to have more
than one bundle around. Remember that Cocoa refers to packages (i.e., folders con‐
taining app resources) as bundles.
You can also get URLs to resources as well:
let resourceURL = NSBundle.mainBundle()
.URLForResource("SomeFile", withExtension: "txt")
// resourceURL is now an NSURL, or nil

What Is an Application?

|

67

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, where‐
as 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 Re‐
sources folder, and for iOS apps, the resources are copied into the root folder of the
application.

The Application Life Cycle
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 life cycle 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—pre-prepared windows, controls, and screens—which are stored in‐
side 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.
For more information on nib files, as well as the related storyboards
and how they’re built, see Chapter 4.

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.
68

|

Chapter 3: Applications on OS X and iOS

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.
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. This is different from objects that
are created by calling their constructor in code.
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 mo‐
ment, 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 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 applicationWillRe
signActive 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 appli‐
cation is still present on the screen. When the application is no longer visible, the ap‐
plication 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 ap‐
plication delegate receives the applicationWillTerminate message, which is sent
The Application Life Cycle

|

69

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),
or opens the task switcher (by double-tapping on the home button). When an applica‐
tion becomes inactive, it’s a signal that it may be exited, so your app should make sure
to save any work.
The iOS application life cycle 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 storyboard.
When the application completes loading, the application delegate receives the applica
tionDidFinishLaunching(_, withOptions:) method. This is similar to the OS X

counterpart, but adds an additional parameter—a dictionary, which contains informa‐
tion about why and how the application was launched.

Applications are most commonly launched directly by the user tapping on the icon.
They can also be launched by other applications, such as when an app passes a file to
another or is opened through a custom URL. The options dictionary contains infor‐
mation that describes the circumstances under which the application launched.
Just as with OS X applications, iOS applications also receive applicationWillResignAc
tive and applicationDidBecomeActive messages (with one difference—on OS X, the
parameter to these methods is an NSNotification object, whereas on iOS the parameter
is a UIApplication).
70

| Chapter 3: Applications on OS X and iOS

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 life
cycle 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 Mac‐
Book Pro is expected to run for around 7 hours on battery, with a full set of applications
(e.g., a word processor, web browser, etc.) loaded and running. An iPhone 6 Plus, by
contrast, is expected to last for greater than 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 6
Plus has only 1 GB, and an iPad Air 2 has only 2 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 (e.g., when the user hits the home button or another appli‐
cation 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:
func applicationDidEnterBackground(_ application: UIApplication)

and
func applicationWillEnterForeground(_ application: UIApplication)

applicationDidEnterBackground is called immediately after the application has

moved to the background state. The application will be suspended after the method has

The Application Life Cycle

|

71

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 earlier, 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 appli‐
cation is using—by freeing large objects, unloading images, and so on.
If possible, try to reduce the amount of memory being used to un‐
der 16 MB. When the application is suspended and the memory us‐
age 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 appli‐
cation won’t be evicted from memory due to another application’s
memory demands. We’ll look at how to measure memory usage in
“Fixing Problems Using Instruments” on page 368.

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 appli‐
cation 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 ap‐
plication delegate:
func applicationDidEnterBackground(application : UIApplication) {
var backgroundTask : UIBackgroundTaskIdentifier! = nil
// Register a background task, and provide a block to run when
// time runs out
backgroundTask = application
.beginBackgroundTaskWithExpirationHandler() {
// This block is called when we're out of time; if we haven't
// called endBackgroundTask before this block returns,
// the application is terminated
application.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}

72

| Chapter 3: Applications on OS X and iOS

let backgroundQueue = NSOperationQueue()
backgroundQueue.addOperationWithBlock() {
// Do some work. You have a few minutes to complete it; by the end,
// you must call endBackgroundTask.
NSLog("Doing some background work!")
application.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
}

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. Introduced with iOS 7 were two means of running tasks in the background: back‐
ground fetching and background notifications.
Background fetching is designed for applications that require periodic updates, such as
weather applications or social networking applications like Twitter. With background
fetching enabled, an application can be woken up in the background to retrieve up-todate 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 the setMinimumBackgroundFetchInterval function
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:
func application(application: UIApplication,
performFetchWithCompletionHandler completionHandler:
(UIBackgroundFetchResult) -> Void) {
// We have 30 seconds to download some data and process it
var error : NSError? = nil
let data = downloadSomeData(&error)
// Once done, let the OS know whether or not new data was
// retrieved or not, or if there was an error

The Application Life Cycle

|

73

if error != nil {
completionHandler(UIBackgroundFetchResult.Failed)
return
}
//
//
//
if

This is a very simple check—your application would
do something more involved, like compare this data against
the most recently downloaded data to determine if it's 'new'
data?.length > 0 {
completionHandler(UIBackgroundFetchResult.NewData)
return
} else {
completionHandler(UIBackgroundFetchResult.NoData)
return
}

}

Background notifications allow your application to receive notifications and process
them in the background. Background notifications could be used in an instant mes‐
saging 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 applica‐
tion will need to be able to handle notifications, which are discussed in “Notifica‐
tions” on page 393, and you’ll need to enable remote notifications in your project.
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 noti‐
fications looks like this:
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject])

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 con‐
taining the data that the remote notification contained.

74

|

Chapter 3: Applications on OS X and iOS