Tải bản đầy đủ - 0 (trang)
Chapter 9. Life Cycle of a Project

Chapter 9. Life Cycle of a Project

Tải bản đầy đủ - 0trang

Additional Simulator SDKs

When the Deployment Target is set to a system earlier than 5.0, earlier Simulator SDK

versions may become available in the Scheme pop-up menu. Exactly what versions

appear depends on the contents of /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/. In Xcode 4.2 and later, you can download and install the iOS 4.3 Simulator SDK from the Downloads pane of the Preferences window (under Components).

If you happen to have earlier SDKs left over from previous versions of Xcode, it may be

possible to install them manually — I’ve done this with iPhone Simulator SDKs back

as far as 4.0, and iPad Simulator SDKs back to 4.2 — but this technique is probably



The app will run only on an iPad.


The app will run on an iPhone or iPod touch; it can also run on an iPad, but not

as a native iPad app (it runs in a reduced enlargeable window, which I call the

iPhone Emulator; Apple sometimes refers to this as “compatibility mode”).


The app will run natively on both kinds of device, and should be structured as a

universal app.

Two additional build settings work together and in conjunction with the Targeted

Device Family to determine what systems your device will run on:

Base SDK

The latest system your app can run on: in Xcode 4.2 and later, you have just two

choices, iOS 5.0 and Latest iOS. As of this writing, Latest iOS means iOS 5.0, so

what’s the difference? It’s that, in the latter case, if you update Xcode to develop

for a subsequent system, your existing projects will use that newer system’s SDK

as their Base SDK automatically, without your also having to update their Base

SDK setting. Latest iOS is the default when you create a new project.

iOS Deployment Target

The earliest system your app can run on: this can be any iOS system number from

the current 5.0 all the way back to 3.0. (iOS 3.0 is also the earliest system on which

a universal app will run.) You can change the iOS Deployment Target setting easily

by editing your project or your target; the project’s Info tab has an iOS Deployment

Target pop-up menu, and the target’s Summary tab has a Deployment Target popup menu. These both represent the iOS Deployment Target build setting; you will

probably want to edit the target, because if you edit the project only, the target

setting will override it.

170 | Chapter 9: Life Cycle of a Project


Writing an app whose Deployment Target differs from its Base SDK is something of a

challenge. The problem is that Xcode will happily allow you to compile using any

features of the Base SDK, but an actual system, whether it’s a Simulator SDK or a device,

will crash your app if it uses any features not supported by that system.

For example, if you were to create a new iPad project using the Single View Application

template and set the iOS Deployment Target to 3.2, and run it on an iPad with iOS 3.2

installed, the app would crash on launch, because the template contains this line, which

is encountered as the app starts up:

self.window.rootViewController = self.viewController;

The problem is that the window rootViewController property wasn’t invented until

iOS 4.0. Here’s an example that should be easier for you to test:

[UIButton appearance];

If that line of code is encountered while running in a 5.0 Simulator, all is well; if is

encountered while running in a 4.3 Simulator, you’ll crash, because the appearance

method wasn’t invented until iOS 5.0.

How can you guard against such problems? I would recommend that you not even

attempt backwards compatibility with a device and system on which you cannot test

directly. If you don’t own an iPad running iOS 3.2, it would surely be unwise to set

your Deployment Target to iOS 3.2; the prospect that a compatibility issue might not

be discovered until the app has been let loose upon a world of users is highly unsettling.

Earlier SDKs can help, to be sure; for this particular example, you might discover the

crash by trying to run the project with the iPad 3.2 Simulator under Xcode 4.0. But

there’s more to testing an app than using the Simulator; some apps, or the discovery

of some bugs, might require a device. The fact is that ensuring backward compatibility

is hard, and you might reasonably decide it isn’t worth the effort.

Writing a universal app presents challenges of its own, because of the physical and

system differences between the iPhone and the iPad. As you develop, you must juggle

two versions of many files, such as nibs. Moreover, although you’ll probably want to

share some code between the iPhone and the iPad version of the app, to reduce duplication, some code will have to be kept separate, because your app will behave differently

on the different types of device. As I already mentioned, you can’t summon a popover

on an iPhone; but the complexities can run considerably deeper, because the interfaces

might behave very differently — tapping a table cell on the iPhone might summon an

entire new screenful of stuff, whereas on the larger iPad, it might only alter what appears

in one region of the screen.

There are various programming devices to govern dynamically what code is encountered, based on what system or device type the app is running on; thus you can avoid

executing code that will cause a crash in a particular environment, or otherwise make

your app behave differently depending on the runtime circumstances (see also Example 29-1):

Choosing a Device Architecture | 171


• The UIDevice class lets you query the current device to learn its system version

(systemVersion) and type (userInterfaceIdiom, either UIUserInterfaceIdiomPhone

or UIUserInterfaceIdiomPad):

if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {

// do things appropriate to iPhone

} else {

// do things appropriate to iPad


For an actual example, make a Universal project from the Master–Detail Application template (with no storyboard) and look in AppDelegate.m. You’ll see how the

code configures the initial interface differently, including loading a different nib,

depending on the device type we’re running on.

• If your app is linked to a framework and tries to run on a system that lacks that

framework, it will crash at launch time. The solution is to link to that framework

optionally, by changing the Required pop-up menu item in its listing in the target

to Optional (this is technically referred to as weak-linking the framework).

• You can test for the existence of a method using respondsToSelector: and related

NSObject calls:

if ([UIButton respondsToSelector: @selector(appearance)]) {

// ok to call appearance method

} else {

// don't call appearance method


• You can test for the existence of a class using the NSClassFromString function,

which yields nil if the class doesn’t exist. Also, if the Base SDK is 5.0 or later, and

if the class’s framework is present or weak-linked, you can send the class any message (such as [CIFilter class]) and test the result for nil; this works because classes

are themselves weak-linked starting in iOS 5.

// assume Core Image framework is weak-linked

if ([CIFilter class]) { // ok to do things with CIFilter

• You can test for the existence of a constant name, including the name of a C function, by taking the name’s address and testing against zero. For example:

if (&UIApplicationWillEnterForegroundNotification) {

// OK to refer to UIApplicationWillEnterForegroundNotification

Many calls that load resources by name from your app’s bundle will automatically select

an alternative resource whose name (before the extension) ends with ~iphone or

~ipad as appropriate to the device type, thus relieving your code from using conditionals. For example, UIImage’s imageNamed: method, if you specify the image name as

@"linen.png", will load an image called linen~ipad.png if it finds one and if we’re running on an iPad. (We’ll see in Chapter 15 that the same sort of naming convention will

help you automatically load a double-resolution image on a device with a double-resolution screen.)

172 | Chapter 9: Life Cycle of a Project



A device can be set by the user to prefer a certain language as its primary language. You

might like the text in your app’s interface to respond to this situation by appearing in

that language. This is achieved by localizing the app for that language.

Localization works through localization folders in your project and in the built app

bundle. Every resource in one of these localization folders has a counterpart in the other

localization folders. Then, when your app goes to load such a resource, it automatically

loads the one appropriate to the user’s preferred language. For example, if there’s a

copy of ViewController.nib in the English localization folder and a copy of ViewController.nib in the French localization folder, the latter will be loaded as the app

launches on a device on which French is the preferred language. So the two copies of

ViewController.nib should be identical except that all the text the user will see in the

interface should be in French in the French version.

This approach solves the problem for resources that are physically loaded, such as nib

files and images and sound files, but it doesn’t deal with strings generated from within

your code, such as the text of an alert message. Surely you don’t want your code to

consist of a bunch of massive if clauses every time there’s text to display. The problem

is solved through the use of a strings file. A strings file is a specially formatted text file

whose file extension is .strings; by default the name of the file is Localizable.strings (that

is, this file will be sought by default, if no filename is specified), but you can use another

name if you like. As with other localized resources, the strings file exists in multiple

copies, one for each language. The strings file consists of key–value pairs; the keys are

the same in all copies, but the values differ, depending on the target language. So instead

of entering a string directly in your code, you tell your code to fetch the correct value

from the appropriate strings file, based on the key:

NSString* myAlertText = NSLocalizedString(@"alertTextKey", nil);

Another specially named .strings file, InfoPlist.strings, stores localized versions of

Info.plist key values. So, for example, the value of the CFBundleDisplayName key, as set

in your project’s Info.plist file, appears under your app’s icon on the user’s device

(Chapter 6); to change this name depending on the user’s primary language setting,

you’d include appropriate key–value pairs in InfoPlist.strings files.

Localization explains the en.lproj folder seen in the Finder in our Empty Window

project folder (Figure 6-8). That’s an English localization folder; its contents, ViewController.xib and InfoPlist.strings, are localized for English. In Xcode, however, nothing seems to indicate this; you wouldn’t know, from looking at the Project navigator,

that there’s anything special about these two files. That’s because there’s only one

localization. As soon as a file has more than one localization, it’s shown in the Project

navigator as a kind of folder, inverted from how it’s shown in the Finder: the file name

contains hierarchically the names of the localizations (Figure 9-1). This makes it easy

to find and edit the correct copy of the file.

Localization | 173


Figure 9-1. How a localized strings file is represented in Xcode

To get started with localization in your project, select in the Project navigator a file that

you want to localize and examine it in the Localization section of the File inspector

(Command-Option-1). It is obvious how to add and remove localization languages


For full discussion, see Apple’s Internationalization Programming Topics.

Editing Your Code

Many aspects of Xcode’s editing environment can be modified to suit your tastes. Your

first step should be to pick a font face and size you like in the Fonts & Colors preference

pane. Nothing is so important as being able to read and write code comfortably! I like

a largish size (14 or even 16) and a pleasant monospaced font such as Monaco, Menlo,

or Consolas (or the freeware Inconsolata).

Xcode has some formatting, autotyping, and text selection features adapted for

Objective-C. Exactly how these behave depends upon your settings in the Editing and

Indentation tabs of Xcode’s Text Editing preference pane. I’m not going to describe

these settings in detail, but I urge you to take advantage of them. Under Editing, I like

to check just about everything, including Line Numbers; visible line numbers are useful

when debugging. Under Indentation, I like to have just about everything checked too;

I find the way Xcode lays out Objective-C code to be excellent with these settings. A

sound approach might be to check everything initially and then, when you’ve some

experience editing with Xcode, switch off features you don’t prefer.

If you like Xcode’s smart syntax-aware indenting, but you find that once in a while a

line of code isn’t indenting itself correctly, try choosing Editor → Structure → Re-indent

(Control-I), which autoindents the current line. (Autoindent problems can also be

caused by incorrect syntax earlier in the file, so hunt for that too.)

Under Editing, notice “Balance brackets in Objective-C method calls.” If this option is

checked, then when you type a closing square bracket after some text, Xcode intelligently inserts the opening square bracket before the text. I like this feature, as it allows

me to type nested square brackets without planning ahead. For example, I type this:

UIAlertView* av = [UIAlertView alloc

I now type the right square bracket twice. The first right square bracket closes the open

left square bracket (which highlights to indicate this). The second right square bracket

also inserts a space before itself, plus the missing left square bracket:

174 | Chapter 9: Life Cycle of a Project


Figure 9-2. The autocompletion menu

UIAlertView* av = [[UIAlertView alloc] ]


insertion point is here: ^

The insertion point is positioned before the second right square bracket, ready for me

to type init.


As you write code, you’ll take advantage of Xcode’s autocompletion feature. ObjectiveC is a verbose language, and whatever reduces your time and effort typing will be a

relief. However, I personally do not check “Suggest completions while typing” under

Editing; instead, I check “Escape key shows code completions”, and when I want autocompletion to happen, I ask for it manually, by pressing Esc.

For example, suppose my code is as displayed in the previous example, with the insertion point before the second right square bracket. I now type init and then press Esc,

and a little menu pops up, listing the four init methods appropriate to a UIAlertView

(Figure 9-2). You can navigate this menu, dismiss it, or accept the selection, using only

the keyboard. So I would navigate to initWithTitle:... and press Return to accept the

selected choice.

Alternatively, I might press Control-Period instead of Esc. Pressing Control-Period repeatedly cycles through the alternatives. Again, press Return to accept the selected


The template for the correct method call is now entered in my code (I’ve broken it

manually into multiple lines to show it here):

[[UIAlertView alloc] initWithTitle:<#(NSString








*), ...#>, nil]

The expressions in <#...#> are placeholders, showing the type of each parameter; you

can select the next placeholder with Tab (if the insertion point precedes a placeholder)

or by choosing Navigate → Jump to Next Placeholder (Control-Slash). Thus I can select

a placeholder and type in its place the actual value I wish to pass, select the next placeholder and type its value, and so forth.

Editing Your Code | 175


Placeholders are delimited by <#...#> behind the scenes, but they appear as “text tokens” to prevent them from being edited accidentally.

To convert a placeholder to a normal string without the delimiters, select

it and press Return, or double-click it.

Autocompletion also works for method declarations. You don’t have to know or enter

a method’s return type beforehand. Just type the initial - or + (to indicate an instance

method or a class method) followed by the first few letters of the method’s name. For

example, in my app delegate I might type:

- appli

If I then press Esc, I see a list of methods such as application:didChangeStatusBarFrame:; these are methods that might be sent to my app delegate (by virtue of its being

the app delegate, as discussed in Chapter 11). When I choose one, the declaration is

filled in for me, including the return type and the parameter names:

- (void)application:(UIApplication *)application


At this point I’m ready to type the left curly brace, followed by a Return character; this

causes the matching right curly brace to appear, with the insertion point positioned

between them, ready for me to start typing the body of this method.


Code autocompletion is supplemented by code snippets, which are bits of text with an

abbreviation. Code snippets are kept in the Code Snippet library (Control-OptionCommand-2), but you can use code snippets without showing the library. You type

the abbreviation and the snippet’s name is included among the possible completions.

For example, to enter an if block, I would type if and press Esc, to get autocompletion,

and select “If Statement”. When I press Return, the if block appears in my code, and

the condition area (between the parentheses) and statements area (between the curly

braces) are placeholders.

To learn a snippet’s abbreviation, you must open its editing window (select the snippet

in the Code Snippet library and press Spacebar) and click Edit. You can add your own

snippets, which will be categorized as User snippets; the easiest way is to drag text into

the Code Snippet library. Edit to suit your taste, providing a name, a description, and

an abbreviation; use the <#...#> construct to form any desired placeholders.

If learning a snippet’s abbreviation is too much trouble, simply drag it from the Code

Snippet library into your text.

176 | Chapter 9: Life Cycle of a Project


Figure 9-3. A warning with a Fix-it suggestion

Live Syntax Checking and Fix-it

Xcode 4 can perform live syntax checking as you type. This feature can save you from

mistakes; in addition, the extremely cool “Fix-it” feature can actually make and implement positive suggestions on how to avert a problem.

For instance, in Figure 9-3 I’ve accidentally omitted the @ before an Objective-C

NSString literal, and the compiler is warning (because what I’ve typed is a C string

literal, a very different thing). By clicking on the warning symbol in the gutter, I’ve

summoned a little dialog that not only describes the mistake but tells me how to fix it.

Not only that: it has tentatively inserted the missing @ into my code. (Note that @ is a

faded gray color. It’s not part of what I typed; Xcode has added it.) Not only that: if I

press Return, or double-click the “Fix-it” button in the dialog, Xcode really inserts the

missing @ into my code — and the warning vanishes, because the problem is solved. If

I’m confident that Xcode will do the right thing, I can choose Editor → Fix All in Scope

(Control-Option-Command-F), and Xcode will implement all nearby Fix-it suggestions without my even having to show the dialog.

Live syntax checking can be toggled on or off using the Enable Live Issues In Editors

checkbox in the General preference pane. Personally, I keep it turned off, as I find it

intrusive. My code is almost never valid while I’m typing, because the terms and parentheses are always half-finished; that’s what it means to be typing. For example, merely

typing a left parenthesis will instantly cause the syntax checker to complain of a parse

error (until I type the corresponding right parenthesis).

The good news is that, starting in Xcode 4.2, turning off live syntax checking doesn’t

eliminate Fix-it. Even if live syntax checking is turned off, when I compile the code

shown in Figure 9-3, I am still shown the same warning and I can still click on the

warning symbol in the gutter to get the same Fix-it suggestion dialog, and I can still

accept its suggestion by pressing Return, by double-clicking, or by choosing Fix All in


Navigating Your Code

Developing an Xcode project involves editing code in many files at once. Xcode provides

numerous ways to navigate your code. Many of these have been mentioned in previous


Navigating Your Code | 177


The Project navigator

If you know something about the name of a file, you can find it quickly in the

Project navigator (Command-1) by typing into the search field in the filter bar at

the bottom of the navigator (Edit → Filter → Filter in Navigator, Command-OptionJ). For example, type xib to see just your nib files. Moreover, after using the filter

bar, you can press Tab and then the Up or Down arrow key to navigate the Project

navigator. Thus you can reach the desired file with the keyboard alone.

The Symbol navigator

If you highlight the first two icons (the first two are dark, the third is light), the

Symbol navigator lists your project’s classes and their methods, making navigation

to a desired method easy. As with the Project navigator, the filter bar can quickly

get you where you want to go. For example, to see the applicationDidBecomeActive: method, type active in the search field.

The jump bar

Every path component of the jump bar is a menu:

The bottom level

At the bottom level (farthest right) in the jump bar is a list of your file’s method

and function declarations and definitions, in the order in which they appear

(hold Command while choosing the menu to see them in alphabetical order);

choose one to navigate to it.

You can add section titles to this bottom-level menu using the #pragma mark

directive. To see an example, make a project based on the Single View Application template; you’ll find this code in ViewController.m:

#pragma mark - View lifecycle

- (void)viewDidLoad


[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.


The result is that the “viewDidLoad” item in the bottom-level menu falls

within a “View lifecycle” section. To make a section divider line in the menu,

type a #pragma mark directive whose value is a hyphen; in this example, both

a hyphen (to make a section divider line) and a title (to make a bold section

title) are used. Similarly, comments outside of any method and starting with

TODO:, ???:, or !!!: will appear in the bottom-level menu.

Higher levels

Higher-level path components are hierarchical menus; thus you can use any

of them to work your way down the file hierarchy.

178 | Chapter 9: Life Cycle of a Project



Each editor pane remembers the names of files you’ve edited in it. The Back

and Forward triangles are both buttons and pop-up menus (or choose Navigate

→ Go Back and Navigate → Go Forward, Control-Command-Left and ControlCommand-Right).

Related items

The leftmost button in the jump bar summons a hierarchical menu of files

related to the current file, such as counterparts, superclasses, and included


The Assistant pane

The Assistant allows you to be in two places at once. Hold Option while navigating

to open something in an Assistant pane instead of the primary editor pane.

The first path component in an Assistant pane’s jump bar sets its automatic relationship to the main pane (tracking). If that relationship involves multiple files,

triangle buttons appear at the right end of the jump bar, letting you navigate between them; or choose from the second path component’s pop-up menu (Control-5). For example, show AppDelegate.m in the main pane and switch the assistant pane’s related items pop-up menu to Includes; the triangle buttons at the right

end of the jump bar then navigate between different files #imported by AppDelegate.m.

You can also be in two places at once by opening a tab or a separate window.

Jump to definition

Navigate → Jump to Definition (Control-Command-J) lets you jump to the definition or implementation of the symbol already selected in your code.

Open quickly

File → Open Quickly (Shift-Command-O) searches in a dialog for a symbol in your

code and the Cocoa headers. You can type the symbol in the search field, or, if a

symbol is selected when you summon the dialog, it will be entered in the search

field for you (and you can then navigate the dialog entirely with the keyboard).


The Breakpoint navigator lists all breakpoints in your code. Xcode 4 lacks code

bookmarks, but you can misuse a disabled breakpoint as a bookmark.


Finding is a form of navigation. Xcode has both a global find (Edit → Find → Find

in Workspace, Shift-Command-F, which is the same as using the Search navigator)

and an editor-level find (Edit → Find → Find, Command-F); don’t confuse them.

Find options are all-important. Both sorts of find have options that you can summon by clicking the magnifying glass. The global find options (Figure 6-3) allow

you to specify the scope of a search (which files will be searched) in sophisticated

ways: choose Custom in the “Find in” pop-up menu to create a scope. The global

Navigating Your Code | 179


find search bar also pops down a menu automatically as you type, letting you switch

among the most important options. You can also find using regular expressions.

There’s a lot of power lurking here.

To replace text, click on the word Find next to the search bar to summon the popup menu, and choose Replace. (It may be necessary to perform a global find first,

before a global replace on the same search term will work.) You can replace all

occurrences, or select particular find results in the Search navigator and replace

only those (click Replace instead of Replace All). Even better, click Preview; it

summons a dialog that shows you the effect of each possible replacement, and lets

you check or uncheck particular replacements in advance of performing the replacement.

A sophisticated form of editor-level find is Editor → Edit All In Scope, which finds

simultaneously all occurrences of the currently selected term (usually a variable

name) within the current set of curly braces; you can use this to change a variable’s

name throughout its scope, or just to survey how the name is used. To change a

symbol’s name throughout your code, use Xcode’s Refactoring feature (see “Making Global Changes to Your Code” in the Xcode 4 User Guide).


Debugging is the art of figuring out what’s wrong with the behavior of your app as it

runs. I divide this art into two main techniques: caveman debugging and pausing your

running app.

Caveman Debugging

Caveman debugging consists of altering your code, usually temporarily, typically by

adding code to dump informative messages into the console.

To see the console as a full window, open a second project window or

tab, show the Debug pane (View → Show Debug Area), and slide the

top of the Debug pane all the way up to cover the editor. Eliminate the

Navigator and Organizer panes, and the variables list. Now this window

or tab contains nothing but the console. Switch to this window or tab

when you want to read the console, but don’t run or stop while viewing

it, as doing so may cause the Debug pane to close or change size.

The standard command for sending a message to the console is NSLog. It’s a C function,

and it takes an NSString which operates as a format string, followed by the format


A format string is a string (here, an NSString) containing symbols called format specifiers, for which values (the format arguments) will be substituted at runtime. All format

180 | Chapter 9: Life Cycle of a Project


Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 9. Life Cycle of a Project

Tải bản đầy đủ ngay(0 tr)