Overtrædelse af konkurrencelovgivningen-U-0004-07





Resumé
Krav om erstatning for overtrædelse af konkurrencelovgivningen skulle bedømmes efter dansk ret
Beta
Book
Agile publishing for agile developers
The book you're reading is still under development. As part of our
Beta book program, we're releasing this copy well before we normally
would. That way you'll be able to get this content a couple of months
before it's available in finished form, and we'll get feedback to make
the book even better. The idea is that everyone wins!
Be warned. The book has not had a full technical edit, so it will con-
tain errors. It has not been copyedited, so it will be full of typos and
other weirdness. And there's been no effort spent doing layout, so
you'll find bad page breaks, over-long lines with little black rectan-
gles, incorrect hyphenations, and all the other ugly things that you
wouldn't expect to see in a finished book. We can't be held liable if you
use this book to try to create a spiffy application and you somehow
end up with a strangely shaped farm implement instead. Despite all
this, we think you'll enjoy it!
Throughout this process you'll be able to download updated PDFs
from your account on http://pragprog.com. When the book is finally
ready, you'll get the final version (and subsequent updates) from the
same address. In the meantime, we'd appreciate you sending us your
feedback on this book at http://pragprog.com/titles/dscpq/errata, or by
using the links at the bottom of each page.
Thank you for being part of the Pragmatic community!
Dave Thomas
Prepared exclusively for Jesper Noehr
Cocoa Programming
A Quick Start Guide for Developers
Daniel H Steinberg
The Pragmatic Bookshelf
Raleigh, North Carolina
Dallas, Texas
Prepared exclusively for Jesper Noehr
Many of the designations used by manufacturers and sellers to distinguish their prod-
ucts are claimed as trademarks. Where those designations appear in this book, and The
Pragmatic Programmers, LLC was aware of a trademark claim, the designations have
been printed in initial capital letters or in all capitals. The Pragmatic Starter Kit, The
Pragmatic Programmer, Pragmatic Programming, Pragmatic Bookshelf and the linking g
device are trademarks of The Pragmatic Programmers, LLC.
Every precaution was taken in the preparation of this book. However, the publisher
assumes no responsibility for errors or omissions, or for damages that may result from
the use of information (including program listings) contained herein.
Our Pragmatic courses, workshops, and other products can help you and your team
create better software and have more fun. For more information, as well as the latest
Pragmatic titles, please visit us at
http://www.pragprog.com
Copyright © 2008 See Contract.
All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmit-
ted, in any form, or by any means, electronic, mechanical, photocopying, recording, or
otherwise, without the prior consent of the publisher.
Printed in the United States of America.
ISBN-10: 1-9343563-0-1
ISBN-13: 978-1-9343563-0-2
Printed on acid-free paper.
B1.0 printing, September 17, 2008
Version: 2008-9-17
Prepared exclusively for Jesper Noehr
Contents
1
Introduction
7
1.1
Moving in . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
1.2
Learning the Language . . . . . . . . . . . . . . . . . . .
9
1.3
Installing the Tools . . . . . . . . . . . . . . . . . . . . .
10
1.4
Exploring the Frameworks . . . . . . . . . . . . . . . . .
13
1.5
In this book
. . . . . . . . . . . . . . . . . . . . . . . . .
13
2
Using What's There
14
2.1
Creating your project in Xcode . . . . . . . . . . . . . .
15
2.2
Creating the appearance with Interface Builder
. . . .
17
2.3
Testing the Interface with the Cocoa Simulator . . . . .
19
2.4
Finishing the Interface . . . . . . . . . . . . . . . . . . .
21
2.5
Wiring up the components . . . . . . . . . . . . . . . . .
23
2.6
Building the project . . . . . . . . . . . . . . . . . . . . .
26
2.7
Exercise: Rinse and Repeat . . . . . . . . . . . . . . . .
27
2.8
The Nib file . . . . . . . . . . . . . . . . . . . . . . . . . .
28
2.9
Exercise: Researching Nibs
. . . . . . . . . . . . . . . .
31
3
Obj-C Insights: Methods and Parameters
34
3.1
Sending Messages without arguments . . . . . . . . . .
34
3.2
Problems sending messages . . . . . . . . . . . . . . . .
35
3.3
Reading the Docs . . . . . . . . . . . . . . . . . . . . . .
36
3.4
Methods with Arguments . . . . . . . . . . . . . . . . . .
39
3.5
Dynamic Binding . . . . . . . . . . . . . . . . . . . . . .
41
3.6
Links back to yourself . . . . . . . . . . . . . . . . . . .
41
4
Creating a Controller
44
4.1
Creating your controller class . . . . . . . . . . . . . . .
45
4.2
Creating an instance of our controller in IB . . . . . . .
47
4.3
Outlets and Actions . . . . . . . . . . . . . . . . . . . . .
49
4.4
Adding Outlets and Actions to the Header . . . . . . . .
51
4.5
Wiring up the Controller . . . . . . . . . . . . . . . . . .
52
Prepared exclusively for Jesper Noehr
CONTENTS
6
4.6
Implementing the Loading of the Previous Page . . . . .
53
4.7
Exercise: Finish the controller . . . . . . . . . . . . . . .
54
4.8
Solution: Finish the controller . . . . . . . . . . . . . . .
54
4.9
Disabling and Enabling the Buttons . . . . . . . . . . .
55
4.10
Still needs work . . . . . . . . . . . . . . . . . . . . . . .
58
5
Listening to Delegates and Notifications
59
5.1
Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
5.2
Setting the Window Title . . . . . . . . . . . . . . . . . .
61
5.3
Exercise: Updating the URL and Setting Buttons . . . .
63
5.4
Solution:Updating the URL and Setting Buttons . . . .
63
5.5
Cleaning up . . . . . . . . . . . . . . . . . . . . . . . . .
65
5.6
Notifications . . . . . . . . . . . . . . . . . . . . . . . . .
67
5.7
Awake from Nib . . . . . . . . . . . . . . . . . . . . . . .
68
5.8
Adding a Progress Indicator . . . . . . . . . . . . . . . .
69
5.9
Spelling Counts . . . . . . . . . . . . . . . . . . . . . . .
71
5.10
Implementing the Load Indicator . . . . . . . . . . . . .
72
5.11
Exercise:Enhancing the Progress Indicator . . . . . . .
73
5.12
Solution:Enhancing the Progress Indicator . . . . . . .
73
6
Obj-C Insights: Objects and Properties
76
6.1
Hello, World . . . . . . . . . . . . . . . . . . . . . . . . .
76
6.2
Creating a new Class . . . . . . . . . . . . . . . . . . . .
77
6.3
Creating a new Object (irresponsibly)
. . . . . . . . . .
79
6.4
Finding Leaks with Instruments . . . . . . . . . . . . .
81
6.5
Reference Counting . . . . . . . . . . . . . . . . . . . . .
84
6.6
The Autorelease Pool . . . . . . . . . . . . . . . . . . . .
85
6.7
Creating a new Object (responsibly)
. . . . . . . . . . .
87
6.8
Refactoring our Code . . . . . . . . . . . . . . . . . . . .
90
6.9
Exercise: Other Initializations . . . . . . . . . . . . . . .
92
6.10
Solution:Other Initializations . . . . . . . . . . . . . . .
92
6.11
Properties . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
6.12
Exercise: Hello GUI . . . . . . . . . . . . . . . . . . . . .
96
6.13
Solution: Hello GUI . . . . . . . . . . . . . . . . . . . . .
97
Index
100
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
Chapter 1
Introduction
As I finished up the final walkthrough of our new house a woman called
to me from across the street. "Tonight's our annual progressive dinner,"
she shouted. "Come meet the neighborhood."
I followed along and met our new neighbors all at once. It went fast
and was a bit overwhelming and there was a ton of information some
of which I was able to sort out later. Mostly, it made me feel a lot better
about my new neighborhood. I knew the questions to ask and I had met
the people who could answer them for me.
That's the goal in this book. It's not a guide for tourists that lists the
things you'd want to see if you were only going to live with Cocoa for
a day. It's not a comprehensive almanac that lists every API class by
class and method by method. This is designed to get you through those
first weeks and months of moving to Cocoa.
This is the coding equivalent of finding out where to go for coffee, which
streets are safe to walk on at night, and which teacher to request for
your kids. Once you get a feel for the neighborhood you'll have more
questions but you'll know where and how to get them answered.
1.1
Moving in
Moving to Cocoa is like moving to a new neighborhood. You need to
figure out where everything is and get used to the local customs. You'll
find some aspects of developing Cocoa apps for Mac OS X are very
similar to what you've been doing while other aspects feel very strange.
In this book you'll get a feel for working with:
· Objective-C--the language of Cocoa development;
Prepared exclusively for Jesper Noehr
MOVING IN
8
What about the iPhone?
In this book we mainly target Mac OS X development. For the
most part, these are the same techniques, tools, and APIs you
will use to target the iPhone. There are differences, but it is fairly
easy to move from Cocoa development for Mac OS X to iPhone
development.
· Xcode, Interface Builder, and Instruments--the tools for Cocoa
development; and
· Cocoa--the frameworks that will give your applications the fea-
tures and polish of your favorite Mac OS X applications.
No one likes a new neighbor who goes on about how good it was where
they came from. It's the same here in OS X. It isn't that the old timers
are being mean. It's just that they have a way of doing things. You
will have an incredible amount of power at your fingertips to quickly
develop native Mac OS X applications if you embrace Obj-C, use the
development tools, and take advantage of the Cocoa frameworks. You
will tend to get much further much more quickly if you use what is
provided for you and follow local customs rather than fight with the
culture.
Use Objective-C. Sure, you can write Cocoa applications in other lan-
guages. But for now, learn the native language. There is a lot of support
for new developers on the various Apple lists1 and in the support docu-
mentation, tutorials, and sample code accessible from Xcode. You will
have an easier time of getting your question answered if you use the
lingua franca of Cocoa development.
Use the tools that Apple provides for Cocoa development. In your old
environment you may have popped open a terminal window and used
vi or emacs along with gcc and gdb or you may have used an IDE
like Eclipse. You'll use Xcode to write, compile, debug, and run your
code, to create, build, and manage your projects, and to create and
1.
For a comprehensive list visit http://lists.apple.com/. You will probably want to subscribe
to the cocoa-dev list. Also look for lists that serve specific areas that you target in your
application. If you have a specific need that is only temporary, you can also search the
archives.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
LEARNING THE LANGUAGE
9
edit your data models. You'll use Interface Builder(IB) to create your
GUI and to wire up the various components. You'll use Instruments to
improve the performance of your application. You can find most of your
favorite command line developer tools in /usr/bin/ but you still want to
use Apple's dev tools when you are creating a Cocoa app.
Finally, use the built-in frameworks as much as you possibly can.
Before you think about writing any code, take a look at what Apple
has provided for you. Your first impulse should always be to use what
is there.
To emphasize this last point, your first project will be to build a simple
web browser with Apple's WebKit framework.2 The browser will include
a text field where the user can enter a URL and a web view that ren-
ders the web page. You will also add "Forward" and "Back" buttons for
navigating through sites you have already visited (see Figure 2.1, on
page 14). Because you are taking advantage of the WebKit framework,
you can accomplish all of this without writing any code.
Working with the new language, tools, and APIs is going to feel a bit odd
at first. They are unfamiliar and so your first instincts won't always be
right. In no time you'll be typing in what you assume the method name
is and find that it is, in fact, correct. Once you tune yourself to the
Cocoa frameworks you'll find that they tend to obey the Principle of
least surprise: you'll usually find what you expect to find.
This is not a comprehensive book in any way. We don't completely cover
every nook and cranny of Objective-C.3 We don't take you click by click
through all that you can do in Xcode nor do we walk through the entire
set of APIs. This book gets you up and running and gives you enough
context to find the answers you need as new questions arise.
1.2
Learning the Language
When you first learned to speak a new language in school you probably
translated everything back and forth from and to your native language.
After a lot of work you begin to master the vocabulary and the grammar
and find that you've gotten comfortable with the native usage patterns
2.
I got the idea for starting with this example while editing Chris Adamson's "ten minute
browser" example in [?].
3.
You can find a quick and comprehensive guide to Objective-C 2.0 in [?].
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
INSTALLING THE TOOLS
10
and idioms. Without noticing it, one day you find yourself thinking in
your new language while you are speaking or reading it.
The same is true about Objective-C, the language of Cocoa. The syntax
is different but much of it is similar to languages you use now. You need
to be as careful of being fooled by the similarities as you are of being
challenged by the differences.4 You can get used to the square brackets
and the way that code is structured pretty quickly. You also have to get
comfortable with the common patterns that Cocoa programmers use.
In Chapter 3, Obj-C Insights: Methods and Parameters, on page 34 we'll
get you comfortable reading some Objective-C. We'll start with mes-
sages because sending messages is the core of Cocoa programming.
Even experienced object-oriented programmers lose sight of this. We
start to think that OO is all about the objects.
Objective-C sits on top of C but it owes many of its ideas to Smalltalk. It
helps to reread Alan Kay's 1998 reminder on Squeak mailing list every
now and then "that Smalltalk is not only NOT its syntax or the class
library, it is not even about classes. I'm sorry that I long ago coined the
term objects for this topic because it gets many people to focus on the
lesser idea.
"The big idea is messaging... The Japanese have a small word ma
for 'that which is in between'--perhaps the nearest English equivalent
is 'interstitial'. The key in making great and growable systems is much
more to design how its modules communicate rather than what their
internal properties and behaviors should be."5
1.3
Installing the Tools
Check that you have installed the free developer tools that came with
Mac OS X. They are included in the Leopard Install disks but are not
installed by default. You can also get the most recent developer tools
(including beta releases) by joining the Apple Developer Connection
(ADC) at http://developer.apple.com. You should join the ADC. There is
4.
One of the great selling points for Java was also it's weakness. It's syntax was familiar.
C programmers could easily write Java code. But they often wrote Java code that looked
a little too much like C code. The same is possible with Objective-C. It sits on top of C so
you could write pure C code. Don't.
5.
http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
INSTALLING THE TOOLS
11
Figure 1.1: New Project Templates
currently a free membership level that gives you the same access to
pre-release software as the paid membership levels.
The developer tools are free. By default the installer puts the developer
applications, documentation, examples, and other files in the Developer
directory at the root level. The examples in this book assume you are
running at least Xcode 3.1.
We'll spend most of our time in this book in Xcode and Interface Builder.
You'll write your code in Xcode and design the data models that we'll
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXPLORING THE FRAMEWORKS
12
Figure 1.2: Helpful tools
use later in the book. You'll use Interface Builder to create the look of
your application and to connect your visual components to the code
containing your business logic.
Even though those are the two applications get get most of the atten-
tion, Figure 1.1, on the preceding page shows you some of the tools
that you get for free. For example, before you release an application
into production, you're going to want to take some time to exercise it
with the profiling tools Instruments and Shark. You'll also find audio
tools, graphic tools, other performance tools, and a slew of utilities.
These are, of course, on top of all of the command line tools you've
installed.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXPLORING THE FRAMEWORKS
13
1.4
Exploring the Frameworks
We're going to play quite a bit with the Cocoa Frameworks. When you
can, you should use the objects and classes that Apple provides before
you struggle to write the code yourself. In the beginning you will find
yourself writing a lot of code to do something you've seen other applica-
tions do on Mac OS X. An experienced Cocoa developer will look at your
code and make a face and suggest "Who don't you just ...". As much as
you may hate to hear it, their two lines of code will do everything that
your 400 lines did. That's just the way it's going to be.
And then one day it will all make sense to you.
That doesn't mean that you will know the two lines of code you need
to write. But fifty lines in you'll be aware that you're working too hard.
you will know that those two lines probably exist. When you don't know
what to do yourself, you'll know where to go to find help.
You can hold your "Option" key down and click on a class name or a
method name to open up the right page in the complete set of docu-
mentation that you've installed. You can right click on a part of your
code and get a pop-up context sensitive help menu like you see in Fig-
ure 1.2, on the previous page. This can include refactorings, links to
definitions and other available actions.
You will find great third party web sites and you will lurk on the cocoa-
dev mailing list that you can find at http://lists.apple.com. Lurk first to get
a feel for the list. And when you have really tried to figure out something
for yourself go ahead and ask a question that explains what you've done
and what you don't understand or are hoping to learn.
Welcome to the neighborhood. I'm so glad you're here. I can't wait to
see the cool applications you develop after you've read this book.
1.5
In this book
This section will describe what is contained in the rest of the book.
This and the rest of the Introduction will be added as the Beta process
continues.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
Chapter 2
Using What's There
We're going to start by creating the web browser you see in Figure 2.1
without writing a line of code.
Our browser won't be fancy but it will mostly work. You'll be able to
type in URLs to load your favorite web sites. You'll be able to navigate
around the sites by following links the way you would in an ordinary
Figure 2.1: A simple web browser
Prepared exclusively for Jesper Noehr
CREATING YOUR PROJECT IN XCODE
15
Figure 2.2: New Project Templates
browser and you'll be able to use a couple of buttons to move forward
and back through your browsing history.
In this chapter we'll use Interface Builder and off the shelf components
to build our browser. We'll pick a couple of buttons, a text field, and a
special component for displaying web pages from a library. We'll drag
these components onto our window and arrange them the way we want
them to look and make sure they visually behave right when we shrink
or stretch the window. We'll then use Interface Builder to enable the
behavior we want. Once we get everything working the best we can for
now, we'll take a quick look behind the scenes at some of the backstage
magic.
2.1
Creating your project in Xcode
As we bounce back and forth between IB and Xcode in these first few
chapters you'll get a feel for what each is for. Mostly, you'll use Xcode
to work on your code, your data model, and your project. The work
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING YOUR PROJECT IN XCODE
16
Figure 2.3: Starting point for SimpleBrowser
you do in Xcode will feel familiar to you if you use other IDEs. As you
might expect, you'll create your GUI using Interface Builder. You'll also
connect the elements of the GUI to data sources, to other GUI elements,
and to methods described in code. You're going to spend almost all of
your time in this first example using Interface Builder.
Even though you won't write any code for this Simple Browser example,
You'll need to create the project from Xcode. Start Xcode and create a
new project using either D B N or File > New Project.
You'll be presented with a variety of options for project templates that
you can use to develop applications for the iPhone or for Mac OS X.
Choose Mac OS X > Applications > Cocoa Application as shown in Fig-
ure 2.2, on the previous page and then press the Choose button or just
press F. Save the project as "SimpleBrowser" in a convenient location.
I've chosen ~/Dev/UsingWhatsThere/.
Your new project should look something like Figure 2.3. There's quite a
bit of infrastructure automatically created for you. In this chapter you'll
do all of your work in the MainMenu.xib file.
Your SimpleBrowser application doesn't do anything yet but you can
take it for a test drive. Press Build & Go and after a moment of compiling
and linking you'll see an empty window with the title "Window" and a
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING THE APPEARANCE WITH INTERFACE BUILDER
17
menu bar populated with the standard set of menus and menu items.
You may also notice that SimpleBrowser.app is no longer highlighted in
red in Xcode. Quit SimpleBrowser and let's get started on creating the
interface.
2.2
Creating the appearance with Interface Builder
Double-click on MainMenu.xib(English) in the Xcode window. This is your
main nib file. A nib file contains all of the work you do in Interface
Builder. The files used to be stored in a binary format and had the
extension nib. They are now stored as XML and have the xib extension.
Despite this change, they are still referred to as nib files (pronounced
"nib" and not "N. I. B.").1
SimpleBrowser consists of a single window with off-the-shelf compo-
nents. You'll fill the MainMenu nib with a web view for rendering the
web sites, a text field for entering their addresses, and two buttons for
navigation.
You started Interface Builder when you double-clicked on MainMenu.xib.
You should see several windows. One has the information for your
menu bar and another is titled MainMenu.xib and contains the "File's
owner" and "First Responder." The one you care about is empty except
for the word "Window" in the title bar. This is the window we'll use to
hold all of our elements.
Open the Library using either D B L or Tools > Library. You can either
navigate to the elements you want or you can use the search box at the
bottom of the window to filter the results. In Figure 2.4, on the next
page I've typed "text" to find the text field box I want to use for URL
entry.
Click on the text field in the library and drag it to your empty window.
As you drag the text field around the window you will see blue guide-
lines that help you place elements. Place the text field in the top left
corner of the window using the left and top guidelines. You may need
to increase the size of the window by dragging the bottom right corner
of the window to the right and down. You need enough room for two
buttons to the right of the text field. Take a look back at Figure 2.1, on
page 14 to be reminded of what you are building.
1.
The name "nib" comes from the acronym for NeXT Interface Builder. Interface Builder
and the framework that has become Cocoa was developed at NeXT Computer.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING THE APPEARANCE WITH INTERFACE BUILDER
18
Figure 2.4: Finding Text elements in the Library
Go back to the Library and clear the word "text" from the search box
on the bottom. Make sure the "Objects" tab is selected at the top and
navigate to Library > Cocoa > Views & Cells > Buttons. You will see a dozen
different buttons. They are all instances of the class NSButton but they
are each used in different situations. As you click on each one, the text
at the bottom of the window changes to let you know which type of
button you have selected. You want the Push button.
Click on the Push button in the Library and drag it to the right of the
text field you just placed in your window. You should see horizontal
blue guidelines that make sure you on the same line as the text field
and as you move the button closer to the text field a vertical blue line
will appear at the left side of the button to indicate that you have the
right separation between the elements.
Go back and grab another Push button from the library and drag it
to the right of the other button. The top row of your window should
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
TESTING THE INTERFACE WITH THE COCOA SIMULATOR
19
now contain a text field and two buttons. We'll be making some more
adjustments to these three elements so you'll have a chance to refine
their size and placement later.
You have one more element to place. Go back to the Library and find
Library > Web Kit. This is not under Cocoa. It is part of a separate frame-
work that you will have to link in to your project later using Xcode. This
framework contains a single visual element called Web View. Click on
the icon that looks like Safari's and drag it into your window. Position
the web view to occupy the rest of the window.
It's easy to get lose the big picture while following all of these "click
here" and "drag there" directions. In this section you've selected the
type of objects you need for your application's interface from a palette
and positioned them in the main window as the user will see them at
launch time.
Save your work and let's take it for a test drive. As you'd expect, you
can save using either D S or File > Save. Close the Library window. You
won't need it for the rest of this chapter.
2.3
Testing the Interface with the Cocoa Simulator
You can pause now and then to play with your interface to make sure
it is behaving the way you want it to. You first have to have Interface
Builder selected as your application (click on any IB window). Start the
Cocoa Simulator using either D R or File > Simulate Interface.
Nothing is wired together so you can't check behavior yet. For now,
all you can test is that the application looks right. Resize the window.
Make it really big. Make it really small. The items probably don't behave
the way you want them to. When you make the window really big you
expect the web view to grow accordingly. When you stretch the window
out wide you expect the buttons and text field to stay close to each
other and to have the text field grow to make up the difference.
The problem is, you haven't told the application how you want the com-
ponents to behave as the window is stretched and shrunk. Quit the
simulator with D Q or Cocoa Simulator > Quit Cocoa Simulator.2
Let's fix how the size of the components change when the window is
resized. You'll need to open the size inspector in IB. You can do this
2.
Note that Quit is not under the File menu as you might expect.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
TESTING THE INTERFACE WITH THE COCOA SIMULATOR
20
Figure 2.5: Using the Size Inspector
with D 3 (You use D 3 because the size inspector is the third tab in the
inspector window.). You can also use your mouse to choose the size
inspector with Tools > Size Inspector. If the Inspector is already open you
can select the third tab from the left. That's the the one with the icon
that looks like a ruler. You can also open the Inspector using D B I or
Tools > Inspector. In any case, you should see something that looks like
Figure 2.5
To change the size settings for a component you select the component
and then make adjustments in the Size Inspector. For now we're mak-
ing adjustments to the "Autosizing" settings. You should experiment
with setting the four struts, the I-beam shapes, on the outside of the
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
FINISHING THE INTERFACE
21
Figure 2.6: Autosizing Buttons, the Web View, and the Text Field
inner square. As you turn them on and off by clicking on them the ani-
mation on the right will show you the results of making the changes.
This is how you control which sides of the component are anchored
when the window grows and shrinks. You should also experiment with
turning the horizontal and vertical springs on and off inside the inner
square. This is how you control the direction in which the component
will stretch and shrink when the window size changes.
Figure 2.6 shows the settings for both buttons, for the web view, and
for the text field. As the window grows and shrinks, the top of each of
the elements will stay the same distance from the top of the window.
The right side of both buttons will stay the same distance from the right
side of the window. The text field will grow horizontally so that its two
sides can stay the same distance from the edge of the window. Finally
the web view will grow horizontally and vertically to fill up the rest of
the window.
Save your work and test the results in the Cocoa Simulator. While you
are here, take a few minutes to experiment with the settings for the
struts and springs and note the results using the Cocoa Simulator.
2.4
Finishing the Interface
You've still got two buttons labeled "Button." One way to change a but-
ton's label is to double-click on the button and type in the new name.
Another way is to use the inspector and select the attributes tab as
shown in the left panel of Figure 2.7, on the next page. This is the
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
FINISHING THE INTERFACE
22
Figure 2.7: Using the Attributes Inspector
inspector's left-most tab--the one with the slider icon. You can also
access this tab using D 1 or Tools > Attributes Inspector.
Set the title for the left-most button to "Back" by selecting the rectangle
to the right of Title and typing "Back". When you select another area of
the inspector you will see the title of the button changes. While you're
at it set the keyboard equivalent for this button to D
by clicking on
the grey rectangle next to the label Key Equivalent and then typing the
left arrow ( ) while holding down the Command key (D). Set the title for
the other button to "Forward" and set its keyboard equivalent to D .
Similarly, you can see in the right panel of Figure 2.7 that we've set the
title of the text field to "http://pragprog.com" either by double-clicking
on it or by selecting it and setting the value of the title field using the
Attributes Inspector. Also use the drop down list to set the value of
Action to Sent on Enter Only. This configures the text field to send its
value when the user presses enter. Where is the value sent? We haven't
set that yet.
You're now done creating all of the visual elements of the interface for
SimpleBrowser. Take another look at it and adjust the sizes of the win-
dow or any of the components. Your browser still doesn't do anything--
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
WIRING UP THE COMPONENTS
23
Reading the Nib
All of the work you have done so far is stored as XML in the
MainMenu.xib file. There is no reason for you to directly read or
change the XML you've generated. This is a format meant to be
created and interpreted by Interface Builder. For kicks, you may
want to open up the file in your favorite text editor and take a
look around. You can find the text field, the two buttons, and
the web view. It's not pretty, and you wouldn't want to have to
interact with your GUI setup this way, but it may be reassuring
that the format is one that you can poke around in if you want
to. Quietly close the file without saving any changes--we'll take
another look at MainMenu.xib before the end of the chapter.
but we will fix that in the next section.
2.5
Wiring up the components
You've used Interface Builder to select and configure the visual ele-
ments you will use in this application. You've placed them in your win-
dow, changed their appearance, set default values, adjusted their sizes,
and set up how they would adjust as the window size changes. Now you
are ready to wire them together.
For example, you want to connect the "Back" button to the Web View.
When you click on the button you want the Web View to display the
contents of the previous URL. Your average button isn't going to know
a thing about web views. A web view, however, should know how to
move back in its history and should be able to be triggered by some
outside source. So you just need to wire up the web views ability to go
back to this outside source.
Similarly, a text field shouldn't know about URLs and web views. If a
text field had to know about everything it could supply text for, the API
would be huge and brittle. But a web view should know how to get a
URL from a string from some other element. So we just need to wire
the web view's "get the URL from a string" ability to the component
providing the string.
It doesn't always work, but it helps to think of who knows what. In this
case most of the knowledge is in the web view. Select the web view in
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
WIRING UP THE COMPONENTS
24
Figure 2.8: Using the Connections Inspector
the layout window and use the Connections Inspector to see what the
web view knows. From the Inspector you can click on the blue right
arrow to open the Connections Inspector tab. You can also type D 5 or
navigate to Tools > Connections Inspector. Figure 2.8 shows the actions
that the web view can receive. Those are the only ones we'll need for
now.
This is the step that feels like magic to me.
Click inside of the circle to the right of goBack: in the Connections
Inspector and hold the mouse button down. Drag the mouse over to the
"Back" button you created. You should see a blue line appear as you
begin to drag. As your mouse hovers over the "Back" button a grey box
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
WIRING UP THE COMPONENTS
25
Figure 2.9: Web View's Connections
will appear with the words "Push Button (Back)". Release the mouse
and you will have made a connection.
Look back at the Connection Inspector. The circle you clicked on next
to goBack: is now filled in. Also goBack: is now visually connected to Push
Button (Back). See the "x" to the left side of Push Button (Back)? Click on it.
You've just broken the connection.
Reconnect goBack: to the "Back" button. Also connect goForward: to the
"Forward" button and connect takeStringURLFrom: to the text field. When
you are done the Connections Inspector should look like Figure 2.9.
You have a working web browser. Save your work and open it up in the
Cocoa Simulator. Click in the text field and hit F and the Pragmatic
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
BUILDING THE PROJECT
26
Programmer home page should appear. Enter another URL and hit F
(don't forget to include http:// as this is just a very basic web browser).
Once that page appears you can hit the "Back" and "Forward" buttons
to navigate back and forth between the two pages.
Our browser doesn't have some of the features you might expect to
see in even the sparest of browsers. For example, you don't get any
feedback when a page is loading. When you first run the SimpleBrowser
you might think that nothing is happening until, after a pause, the page
appears. Also, the URL doesn't change when you hit back and forward.
If you load http://pragprog.com and then http://apple.com and then hit
the back button, you will see The Pragmatic Programmer's home page
even though the URL will still read http://apple.com. We'll take care of
these problems in a few chapters.
2.6
Building the project
It feels like you're done. You can play with your application in the Cocoa
Simulator. All of your work in Interface Builder is correct and complete
and you have no code to write for this SimpleBrowser project. You don't,
however, have an application that you can give someone else to run on
their machine. You need to go back to Xcode and create a release.
In Xcode choose "Build & Go." The build succeeds and it looks as if
SimpleBrowser starts to load. The icon bounces in the dock for a while
but nothing is happening.
Arrggghhhh. Click the stop button. We need to add the WebKit.framework.
I mentioned earlier we'd have to link it in. We could have done it when
we dragged the WebView from the Library into our window. Chances are
that you'll be more likely to remember it now that you see the problems
that can come up. See the Joe Asks. . . on page 28 for an explanation
of how you might have diagnosed the problem yourself.
When we dragged the WebView instance from our library we saw that it
is part of the Web Kit framework but not part of the Cocoa framework.
I would have assumed that Xcode would automatically have added the
Web Kit framework to the corresponding project to link against but it
doesn't. You need to do that yourself.
From the project view, right-click or control-click on the SimpleBrowser
group and choose SimpleBrowser > Add > Existing Frameworks ... as shown in
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: RINSE AND REPEAT
27
Figure 2.10: Adding a Framework
Figure 2.10. Choose the WebKit.framework and make sure that Simple-
Browser is checked in the target list.
Make sure that your Active Build Target is set to "Release" and not
"Debug". You can do this from Project > Set Active Build Configuration >
Release or using the Active Build Configuration drop down in Xcode if it
is visible.
Once the build target is set, press "Build & Go" again. This time your
SimpleBrowser application should build, link, and launch fine. Look
inside your projects build/Release directory and you should see a version
of your SimpleBrowser application that can be distributed and run on
other machines.
2.7
Exercise: Rinse and Repeat
In this chapter you created a working web browser without writing any
code. You took advantage of Apple's Web Kit framework to do most of
your work for you. You spent much of the time getting the application to
look right and just a few click-and-drags at the end to get the behavior
you need.
You learned how to make this all work in discrete steps but that's not
the way you will tend to work. If you were doing this again from scratch
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
THE NIB FILE
28
Joe Asks. . .
How would I have known to add WebKit framework?
Start by opening up the debugger with either Run > Debugger or
B D Y.In the top left corner of your Xcode window and you will
see the warning that the application is
TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION
The stack trace isn't very helpful, but if you look at the Console
you will see the problem pretty quickly. Open the Console using
D B R or Run > Console. You'll see a gdb prompt. Scroll back past
a sequence of ten digit numbers that make up the stack. Just
above this you will see a series of lines that each begin with
a time stamp, the word "SimpleBrowser" and brackets around
an identifier that includes the process number. Once you pare
away all of this information you should be left with something
like this:
An uncaught exception was raised
-[NSKeyedUnarchiver decodeObjectForKey:]:
cannot decode object of class (WebView)
Terminating app due to uncaught exception
'NSInvalidUnarchiveOperationException'
Aha! The problem is with unarchiving a WebView object. Your
Nib is essentially an archive of objects and their connections.
You need to load the Web Kit framework so that the WebView
can be successfully reconstituted.
you would combine steps. When you drag in your first button you would
probably immediately name it "Back" and connect it to the web view.
Although you will be tempted to skip this exercise, you will benefit
greatly from starting from scratch and recreating this web browser.
With no one telling you step by step what you need to do next, the
pieces will start to fit together. Take five minutes to make your own web
browser and you will reinforce what you have learned.
2.8
The Nib file
All of the work you did in this chapter is captured in a Nib file. A Nib is
an archive of objects. At build time the MainMenu.xib Interface Builder
source file is compiled into MainMenu.nib. In Cocoa, nib files contain all
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
THE NIB FILE
29
Figure 2.11: Templates for NSButtons
of the information you need to bring your UI elements to life at runtime.
A typical Cocoa application will have many Nib files that are only loaded
as they are needed to create instances of the objects that make up your
user interface and, as you'll see in Chapter 4, Creating a Controller, on
page 44, non-visual objects as well.
The Nib file represents the graph of objects you built using Interface
Builder so that your interface can be reconstructed each time your
application begins. Other platforms use this to serialize objects before
sending them over the wire or persisting them to disk. Let's take a quick
look back at creating and using the nib we built in this chapter.
Our particular Nib contains two buttons which are each instances of
the class NSButton. If you search the Interface Builder Library for NSBut-
ton you won't just find one button. You should see the dozen buttons
shown in Figure 2.11.
You could start with any one of them and turn them into the instance
of NSButton that you want. But Apple wants you to use what's there.
That means that you aren't really focused on finding an NSButton as
you browse through the library. You are looking for a button that has a
particular look or is used in a particular way. This means that you look
for components from your end-users point of view.
You select, place, and configure your button. You can figure out at least
part of the information that is stored by reading the xml. If you haven't
already, open MainMenu.xib with a text editor. Search for the text NSBut-
ton and look for this part of the file that describes the look of your
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
THE NIB FILE
30
"Back" button.
Download UsingWhatsThere/SimpleBrowser/partial/MainMenu.xib
<object class="NSButton" id="164086064">
<reference key="NSNextResponder" ref="439893737"/>
<int key="NSvFlags">265</int>
<string key="NSFrame">{{277, 258}, {93, 32}}</string>
<reference key="NSSuperview" ref="439893737"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="941085700">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">134217728</int>
<string key="NSContents">Back</string>
<reference key="NSSupport" ref="640083843"/>
<reference key="NSControlView" ref="164086064"/>
<int key="NSButtonFlags">-2038284033</int>
<int key="NSButtonFlags2">268435585</int>
<string key="NSAlternateContents"/>
<string type="base64-UTF8" key="NSKeyEquivalent">75yCA</string>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
</object>
This describes an object of type NSButton. The x and y coordinates as
well as the height and the width are in the line with the NSFrame as the
key. The NSButton object also contains an object of type NSButtonCell. We
haven't talked about NSButtonCells but you can see that that's where the
button name and the keyboard shortcut is set.
The Nib file also contains objects that represent the connections you
made. For example search for the string takeStringURLFrom:. you should
see this part of MainMenu.xib.
Download UsingWhatsThere/SimpleBrowser/partial/MainMenu.xib
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">takeStringURLFrom:</string>
<reference key="source" ref="1029174864"/>
<reference key="destination" ref="109215417"/>
</object>
<int key="connectionID">459</int>
</object>
You can search on the reference numbers for the destination and the
source and see that this snippet describes a connection from the Web-
View object with label takeStringURLFrom: to the destination the NSTextField
object. In the next chapter we'll learn to say this a little differently.
As you'll see in the next chapter, this just means that the target (the
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: RESEARCHING NIBS
31
Figure 2.12: Mail's nibs
WebView object) is being sent the message takeStringURLFrom: with the
WebView object passed in as the sender.
In our particular case the Nib object life cycle is straightforward. When
the application starts up the object graph is loaded into memory and
unarchived. The components are initialized. Then all of the connections
between the objects are made and the main menu is displayed.
2.9
Exercise: Researching Nibs
Apple provides several resources to help you find out more about nibs.
They have produced a "Resource Programming Guide". You can find
this document by searching for the title from Xcode's Help > Documenta-
tion menu item.
That's the boring way. Sure you'll learn all the steps and all the details
of what's going on under the covers--but you're a programmer. You
don't read manuals. You poke around and play with what you find.
Go to Apple's Mail application. You'll find it at /Applications/Mail.app.
Open up the package by control-clicking on Mail.app and choosing Show
Package Contents from the pop-up menu. You should see something like
Figure 2.12.
Do you see all of those nib files inside of Contents/Resources/English.lproj?
Double-click on one and open it up. At the time of this writing you can
look at any of the nibs in Mail and explore their view of how much a nib
file should contain and how they should be constructed. You can look
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: RESEARCHING NIBS
32
Figure 2.13: Nib warning
inside of many of your favorite Mac OS X applications and explore the
nib files.
You won't always be able to view the contents of the nib files. To see
what can go wrong let's look at the SimpleBrowser application you just
built (SimpleBrowser/build/Release/SimpleBrowser). Show its package con-
tents and look inside of Contents/Resources/English.lproj. If you used the
default project settings in Xcode, the icon to the left of MainMenu.nib
looks different than the ones you saw in Figure 2.12.
Try to open MainMenu.nib in Interface Builder and you should get the
error like the one shown in Figure 2.13.3
We can fix this back in Xcode by adjusting one of the build settings for
this project. In Xcode select the menu item Project > Edit Project Settings.
Near the bottom under the heading Interface Builder Compiler uncheck
the check box labeled Flatten Compiled XIB Files.
Close your open Finder windows that display the package contents of
the SimpleBrowser. In Xcode build the project again. If you are compul-
sive like me you may want to clean up first by choosing Build > Clean All
Targets before rebuilding the project. Now reopen the package for Sim-
pleBrowser and find the nib file. You should now be able to open this
nib file using Interface Builder.
3.
Depending on your settings and what you have installed, the exact wording of your
errors and warnings may differ from mine. Don't worry as the point being illustrated is
the same.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: RESEARCHING NIBS
33
So the cool thing is that you have the ability to learn from the work
of others so long as they haven't chosen to flatten their nib files before
distributing their applications. It helps you see just how much of the
application is expressed as nib files.
Of course any useful application has its share of code as well--that's
the next stop on our tour.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
Chapter 3
Obj-C Insights: Methods and
Parameters
So far you have created visible objects and sent messages from one
object to another using visual tools. In Chapter 4, Creating a Controller,
on page 44 you will customize the behavior of these objects by writing
your own code. Before you start writing your own methods, let's get you
comfortable reading the Objective-C that sends a target an action and
navigating the API documentation to find out which messages you can
send to an object.
3.1
Sending Messages without arguments
Back in Section 2.5, Wiring up the components, on page 23 you used
Interface Builder to create a connection from your "Back" button to the
goBack: "Received Action" in your web view. As a result, every time the
"Back" button is pressed the web view's goBack method is called1.
Let's assume you have instantiated the two objects involved in this
action. You have an instance of NSButton named backButton and an
instance of WebView named myWebView. When the user presses the
"Back" button essentially this message is sent:
[myWebView goBack]
1.
You might notice that I've introduced a slight simplification by dropping the ":" at
the end of goBack. I'll fix that in a little bit, but it's going to make it easier for us in the
beginning.
Prepared exclusively for Jesper Noehr
PROBLEMS SENDING MESSAGES
35
The square brackets and everything in between make up the Message
expression. In this simplest of cases there are only two parts. The myWe-
bView object is the receiver. It is the target--the object that you are
sending the message to. In this example, the message consists entirely
of goBack.
We are sending a message with no arguments. In this form, the call will
look like this:
[receiver message]
Depending on the programming language you use now, this might look
like a function call or method call. In other words, in your own language
the Objective-C message
[myWebView goBack]
looks something like
myWebView.goBack()
As you would expect, you can chain messages together in much the
same way you would chain methods. In Java you might write
myWebView.oneMethod().anotherMethod()
This invokes oneMethod() on myWebView and then anotherMethod() is
called on the result.
In Objective-C you would write this same fictional code as
[ [myWebView oneMethod] anotherMethod]
3.2
Problems sending messages
There are two basic things that could go wrong with a simple message
in Objective-C: either the receiver may not exist in which case it has
the value nil or the object may be valid but may not understand the
message being sent to it. You may get a compiler warning when you try
to build the application but neither type of error will break the build or
prevent someone from trying to run the application.
In the first case you are sending a message of the form
[nil someMessage];
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
READING THE DOCS
36
Not only is there no error or exception at runtime, if someMessage
returns an object, pointer type or most valid numeric types, then this
message returns 0.2
In the second case you are sending a message like this.
[validObject someMessageItDoesNotUnderstand];
This time the application will terminate when the receiver is sent a mes-
sage it doesn't understand. If validObject is an instance of the fictional
class CustomClass you'll see a message like this in the console.
*** -[CustomClass SomeMessageItDoesNotUnderstand]:
unrecognized selector sent to instance 0x109280
*** Terminating app due to uncaught exception 'NSInvalidArgumentException'
There are times that we will take advantage of the dynamic typing and
dynamic binding and other times that we will find it helps to take
advantage of the help the compiler can give us if it knows the types
we are targeting. One way to avoid runtime errors is to become famil-
iar with Apple's docs so that you are sending objects messages they
understand.
3.3
Reading the Docs
Apple includes a comprehensive set of documentation to help you figure
out what to use and how to use it when you develop your Cocoa Apps.
You can access the docs from Xcode using Help > Documentation. Of
course you can make your way through the documentation without
my help--I just want to point out some of what you'll find as a way of
encouraging you to look.
Type "webview" into the search box in the upper right corner of the Doc
viewer and experiment with the different results you get depending on
whether you choose to search through the APIs, the titles of documents,
or through the full text. You can also choose whether you are only
interested in results that start with, contain, or match the search string
exactly.
You can narrow your search in other ways. For now choose API, All Doc
Sets, All Languages, and Starts With. Your first two results should be
the webView method and the WebView class.
2.
For more details see the "Objective-C 2.0 Programming Language" included in the
Apple documentation.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
READING THE DOCS
37
Figure 3.1: API Document Organization
Select the line containing the WebView class to see the class reference
for WebView. The top of the listing is shown in Figure 3.1.
On the right side you can trace the inheritance from WebView back
to the Cocoa root class NSObject. You also see what Framework you'll
need to include to use the class. Objective-C uses header files3--in this
case WebView is declared in WebView.h. The "Availability" section of the
method description will let you know whether a method is available if
you want to target older versions of Mac OS X.
You can search the document for specific method names, but there are
three links in that grey box on the left side that will help you figure out
how to use a Cocoa class.
· Overview--This is a quick summary of what the class is for and
how you should use it. It contains links to specific methods that
need to be called out and classes that often collaborate with the
one being described.
· Tasks--You can get alphabetical listings of the class and instance
methods below but this is an organization of the methods into
3.
If you aren't familiar with header files, don't worry. Section 4.4, Adding Outlets and
Actions to the Header, on page 51 and the material leading up to it should give you a good
feel for how and why they are used.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
READING THE DOCS
38
Figure 3.2: API Document Organization
tasks you want to perform. For example, Figure 3.2 shows the
methods used for moving back and forward.
· Companion Guide--Apple often has one or more comprehensive
documents that gives examples of using instances of this class to
accomplish some programming task. In this case you are given a
link to the "Web Kit Objective-C Programming Guide."
In addition to the Tasks listing of all the methods, you can also see a
list of the Class methods and a list of the Instance methods. There are
also lists of constants and available notifications.
Let's take a quick look at a listing for a method. If you click on the
goBack method you'll see something like Figure 3.3, on the following
page--a quick description of what the method is designed to do followed
by the method signature.
- (BOOL) goBack
The "-" indicates that goBack is an instance method. A "+" is used to
label a class method. The return type is BOOL. You can see by the "Dis-
cussion" section that in Objective-C the two boolean values are YES and
NO and not true and false.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
METHODS WITH ARGUMENTS
39
Figure 3.3: A Method Listing
3.4
Methods with Arguments
When you connected your "Back" button to the web view you didn't
select the goBack method. You actually chose the goBack:, which is a
completely different method4. The trailing colon on the goBack: method
indicates that it takes a single argument.
Look up the goBack: method in the WebView class reference. Its signa-
ture is very different than the no argument goBack method.
- (IBAction) goBack:(id) sender
You'll see this type of signature a lot with methods designed to be called
by GUI components. The sender argument is a handle back to the calling
object. The type of sender is upcast to id This is the generic type for
Cocoa. Any pointer to an object is at least of type id. This allows us
to have a handle to any object that called goBack: not matter what it's
type. We'll look a typical example of how you might take advantage of
4.
You didn't have any choice in the matter. Interface Builder restricted your option to
this second version.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
METHODS WITH ARGUMENTS
40
this in Section 3.6, Links back to yourself , on the next page. The IBAction
return type will give us hooks from code written in Xcode into objects
created with Interface Builder. We'll cover IBAction and its dual IBOutlet
in Section 4.3, Outlets and Actions, on page 49.
If you are still translating back and forth between Objective-C and
another language,
[myWebView goBack:self]
looks something like
myWebView.goBack(self)
Let's use a method with more arguments to help you better understand
the differences. WebView contains a method with the rather lengthy
name searchFor:direction:caseSensitive:wrap:. That is the entire method
name. In other languages this might be called searchFor but in Objective-
C the description of all of the arguments is included as part of the
method name. To be more formal, each piece that ends with a colon is
called a keyword. When you call the method you need to supply one
argument for each keyword following the colon. So searchFor: is a key-
word as is direction:, caseSensitive:, and wrap:.
The entire signature of the method is
- (BOOL)searchFor:(NSString *)string
direction:(BOOL)forward
caseSensitive:(BOOL)caseFlag
wrap:(BOOL)wrapFlag
Here's how you might use it.
[myWebView searchFor:myString direction:YES caseSensitive:NO wrap:YES]
The equivalent in Java might be something like this:
myWebView.searchFor(myString, true, false, true)
The Objective-C version might look like named parameters but they
aren't. The order of the arguments can not be changed. The method-
name is searchFor:direction:caseSensitive:wrap: which is also called the
selector because it is used at runtime to select the method that will
be called.
You will come to really appreciate the fact that you don't have to think of
what the true, false, and true refer to. In the Obj-C version you know that
you are conducting a search that is not case sensitive in the forward
direction with wrapping enabled.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
DYNAMIC BINDING
41
Your Cocoa code should be readable. A month after you've written a
method, you should be able to quickly reconstruct your intent and
understand what the method does. In WebView you can see the method
moveToBeginningOfSentenceAndModifySelection:. You might end up The
name of a method might even be longer than its implementation--but
what the method does is immediately clear to anyone who invokes it in
code or connects to it using Interface Builder.
3.5
Dynamic Binding
Let's take a look behind the scenes at what happens when an an Obj-C
message is sent. The simplest of cases
[myWebView goBack]
is converted at runtime to the function call
objc_msgSend(myWebView, goBack)
The receiver is passed in as the first argument and the selector as the
second. The more complicated message
[myWebView searchFor:myString direction:YES caseSensitive:NO wrap:YES]
is converted at runtime to this function call
objc_msgSend(myWebView, searchFor:direction:caseSensitive:wrap:,
myString, YES, NO, YES)
Again the receiver is passed in as the first argument and the selector
as the second. The parameters are passed in as the remaining function
arguments. Here's more than you need to know. At runtime the selector
is matched to an entry in the dispatch table for the object's class which
points to the memory location of the procedure that implements the
requested method. If no selector exists in that class then the search
continues in the class' superclass and on up the tree to the root class.5
3.6
Links back to yourself
We used the WebView method takeStringURLFrom: to use the URL the user
types into the text field for the web view. The takeStringURLFrom: signature
looks a lot like the goBack: method's.
- (IBAction)takeStringURLFrom:(id)sender
5.
For more information on this read Apple's "The Objective-C 2.0 Programming Lan-
guage."
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
LINKS BACK TO YOURSELF
42
Figure 3.4: SimpleBrowser with more text fields
Assume that we have an instance of WebView named myWebView and
the text field is an instance of NSTextField named addressField. Then when
the user enters a string in the textfield and hits enter something like
this message is sent:
[myWebView takeStringURLFrom: self]
The web view receives this message and sends a message back to the
object sending the takeStringURLFrom: message asking for its string value.
[sender stringValue]
The web view then tries to load this URL.
You can see this in action by modifying the SimpleBrowser project. Add
two text fields with different default addresses (see Figure 3.4).
In Interface Builder, select the Web View and use the Inspector to look
at its connections. There should be a connection between the received
action takeStringURLFrom: and your first text field and the circle should
be filled in as before.
But we know how takeStringURLFrom: works. The first thing it does is send
a message back to the component that called the method. So there's no
reason we can't connect this received action to multiple elements. Drag
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
LINKS BACK TO YOURSELF
43
Figure 3.5: One to many relationship
from the filled in circle to one of the two text fields you added at the
bottom of the window. Drag one more time from the filled in circle to
the other text field you added. Your Web View is now ready to take its
URL from any of the text fields. You can see the "Multiple Selections" in
Figure 3.5.
Save your work and test your modified application. You can enter URLs
from any of the text fields. In fact, you could have mix and matched
the type of visual components that you used to pass in the URL. The
component just needs to be able to respond to the message stringValue
with a string containing a valid URL.
Now that you know how to read methods in Objective-C it's time to write
a few of your own to customize the behavior of the SimpleBrowser.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
Chapter 4
Creating a Control er
You can't accomplish everything you want your application to do just by
dragging connections between the visual elements in Interface Builder.
On the one hand, it's pretty amazing how easily we can create a simple
web browser just using visual tools. On the other hand, the browser
leaves a lot to be desired. There are some things you're just going to
need to code up yourself.
Cocoa programming separates the application logic from the look and
feel using Model-View-Controller (MVC). For the model, we'll create the
application logic in Objective-C using Xcode. You've already seen that
we create the view using Interface Builder.
The controller is the bridge between the model and the view. When
the user clicks on a button or types in a text field or does anything
to the view, the controller communicates these actions to the model.
Similarly, when the model changes, the controller updates the view so
that the changes are visible to the user.
The controller has to have a foot in each world. There is a class file
that you use to create methods and send messages to the model or the
view. There is also a visual representation of the controller that lives in
Interface Builder that you use to wire the controller's code to the visual
components you create in IB. It's sort of like having the real version of
the controller living in code and its Second Life avatar living in IB.
In this chapter we'll create a controller for our SimpleBrowser exam-
ple. To keep things simple, we won't have a model--we'll just have a
view and a controller. In a way, most of the application logic is being
managed by the web view. Just as with the SimpleBrowser example in
Chapter 2, Using What's There, on page 14, we'll create this controller
Prepared exclusively for Jesper Noehr
CREATING YOUR CONTROLLER CLASS
45
Reusing your NIB
You can continue to work with the SimpleBrowser project or you
can create a new project and copy over your NIB file. I created
a new Cocoa Application and named it "SimpleBrowserWith-
Controller". I then used the Finder to locate the NIB file from
SimpleBrowser. It's at .../SimpleBrowser/English.lproj/MainMenu.xib.
Copy MainMenu.xib and paste it over .../SimpleBrowserWithCon-
troller/English.lproj/MainMenu.xib.
Back in Xcode you can double-click on MainMenu.xib and Inter-
face Builder will open up with the configuration you created in
the previous project. Before you forget, you are going to have
to add the Web Kit framework again or you will get errors build-
ing in Xcode.
in small discrete steps to explain all of the new concepts. You'll combine
and rearrange once you understand what you're doing.
4.1
Creating your controller class
All classes are created in Xcode.1 From Xcode choose File > New File ...
or D N. Choose to create a Cocoa > Objective-C class. The description
should say that you are creating "An Objective-C class file, with an
optional header which includes the <Cocoa/Cocoa.h> header." I know
this doesn't look like a controller class and there are other options that
include the word "Controller". Don't choose them. What makes this
class a controller is how you will configure and use it.
Name your class SimpleBrowserController and make sure that the check-
boxes to create SimpleBrowserController.h and to target SimpleBrowserWith-
Controller are checked.2 Generally, if you accept the defaults you should
be ok. Click Finish.
You've now generated a header file SimpleBrowserController.h and also an
implementation file SimpleBrowserController.m. Here's the header file with
1.
If you have used Xcode and IB in the past you'll note that this is a change. You'll also
see, in this chapter, that you no longer have to drag your header files back into IB every
time you make a change. The synchronization is done for you.
2.
I'm going to assume you are working with a new Cocoa Application project that you've
named "SimpleBrowserWithController".
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING YOUR CONTROLLER CLASS
46
two comments I've inserted to help our discussion:
Download CreatingAController/SimpleBrowserWithController/partial/SimpleBrowserController1.h
#import <Cocoa/Cocoa.h>
@interface SimpleBrowserController : NSObject {
// you'll declare instance variables here
}
// you'll declare methods here
@end
The header file contains the public interface for the SimpleBrowserCon-
troller class. You use it to tell other people how they can interract with
your class. At the top of the file you often import the header files of other
classes your class might want to use. In this case Xcode has already
included the directive to import Cocoa.h which includes the header files
for all of the Cocoa classes you might need to use.
Everything between @interface and @end is the description of the public
interface for the SimpleBrowserController class.
SimpleBrowserController : NSObject indicates that the SimpleBrowserController
class directly extends the root class NSObject. WebView, in contrast,
was a subclass of NSView which in turn was a subclass of NSResponder
which is a subclass of NSObject (see Figure 3.1, on page 37). Unless
you specifically override it, you benefit from inheriting the behavior of
any of your super classes. The behavior that is common to all objects is
specified in the root class NSObject. You will sometimes have to look in
the documentation of a superclass to find methods that are available to
objects created from your class.
In the header file, the comments I've added to the template code show
that you add the declaration for your instance variables inside the curly
braces and you declare your methods between the closing brace and the
@end.
Here's SimpleBrowserController.m, the implementation3 file you just gen-
erated,
Download CreatingAController/SimpleBrowserWithController/partial/SimpleBrowserController1.m
#import "SimpleBrowserController.h"
3.
Note the suffixes of the two files SimpleBrowserController.h and SimpleBrowserController.m.
The "h" is for header and "m" is for implementation. You'll find this and other fun facts
in the Objective-C FAQ at http://www.faqs.org/faqs/computer-lang/Objective-C/faq/.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING AN INSTANCE OF OUR CONTROLLER IN IB
47
@implementation SimpleBrowserController
@end
The file begins by importing the header file for SimpleBrowserController.
Other than that you will see beginning and end markers for the class
implementation. Notice that in this file you don't specify that the Sim-
pleBrowserController inherits from NSObject.
Save your work.
Our next step is to create an instance of the class and allow it to interact
with the GUI elements you've already created. You can instantiate Sim-
pleBrowserController using code you write in Xcode or in much the same
way we instantiated the GUI elements like NSButton in Interface Builder.
We will create objects that belong to the model in Xcode as they don't
need to directly know about or communicate with any of the GUI ele-
ments. We will create objects that are controller elements in Interface
Builder so that we can drag connections between the controllers and
the objects they communicate with.
4.2
Creating an instance of our controller in IB
We're now going to create an instance of SimpleBrowserController in Inter-
face Builder.4
When we created instances of our buttons we just looked in the Library
for the NSButton that looked like the one we wanted and dragged it into
our window. We can't do that with our SimpleBrowserController for a cou-
ple of reasons.
· There is no way that Interface Builder would know about our Sim-
pleBrowserController class--we just made it up. So we'll have to grab
a generic NSObject from the Library. You will find controllers to
choose from as well but remember that SimpleBrowserController is a
subclass of NSObject so that's what you'll use initially to represent
it.
4.
Actually the instance isn't really created until the Nib is unarchived when the appli-
cation starts up. We can think of the instance being created at this point in the same
way we will talk about creating an object in code when we learn to use a call like this
[[SimpleBrowserController alloc ] init ].
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING AN INSTANCE OF OUR CONTROLLER IN IB
48
Figure 4.1: The Document window
· We'll need a way of letting Interface Builder know that this object
is not just an NSObject but that it is actually an instance of Simple-
BrowserController.
· The SimpleBrowserController object won't be visible to the users. We
don't want to drag our object representation into the window the
way we dragged and positioned buttons, the text field, and the web
view. We need another place to put it.
Let's start with that last point. Double-click on MainMenu.xib (under
Resources in the side menu) to open the NIB file in IB. You aren't inter-
ested in the Window view any more as there is no visual representation
of the controller for the end-user to see. Instead, bring up the Document
window in Interface Builder with the key sequence D 0 or Window > Doc-
ument. In Figure 4.1 you can see the higher level visible and non-visible
components of SimpleBrowserWithController. We'll add our controller
to this document.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
OUTLETS AND ACTIONS
49
Figure 4.2: Assigning the Identity of the Controller
Next we need something to represent our SimpleBrowserController object.
Go to the Library and look in the Cocoa > Objects and Controllers group.
Click on NSObject (which is called Object in the list) and drag it into the
Document. As before, you will be tempted to drag one of the controllers
in but don't--choose Object.
Now we need to change the object from being an instance of NSObject
to being an instance of SimpleBrowserController. Click on the Object icon
you just dragged into the Document and look at the Identity Inspector
for it by clicking the i icon in the blue circle at the top of the inspector
window (see Figure 4.2). On your screen you'll see the Class Identity
listed as NSObject. In its place enter SimpleBrowserController as shown in
the figure. Enter, or scroll down and select from the list. Save your
work. You have created a controller object.
4.3
Outlets and Actions
There are basically two ways in which the controller connects to UI
elements:
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
OUTLETS AND ACTIONS
50
Figure 4.3: Outlets and Actions
· Actionscontroller methods used when an element such as a but-
ton wants to initiate an action performed by the controller.
· Outlets--controller instance variables that point to the UI ele-
ments the controller needs to send messages to.
Actions and Outlets are specifically designed for connections created in
Interface Builder. In Figure 4.3 you get an idea of the basic flow. When
a user presses a button a message is sent to a specified target to initiate
a specific action. You create this target action in a controller and you
make the connection in interface builder. The action is just a method
that will get called when the button is pressed.
There are times the controller is going to need to communicate with an
object you created using IB. One way is to give the controller a handle to
the object. In Figure 4.3 you see that the controller has an outlet which
is a text field. In other words, the controller has an instance variable
that points to the text field. Just like a wall socket, the outlet is a place
in the controller where the visual element plugs in to.
Our SimpleBrowserController doesn't have any outlets or actions yet. In
the next section you will modify your header file to add some. Once you
save your changes in Xcode, Interface Builder will automatically pick
up these changes and allow you to drag and drop connections to and
from the other elements you have created.
The signature of the Actions look a lot like the goBack: method we exam-
ined in Section 3.4, Methods with Arguments, on page 39:
- (IBAction) methodName:(id) sender;
The corresponding declaration of an Outlet looks like this:
IBOutlet ClassName *variableName;
Note that variableName is a pointer to an object of type ClassName.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
ADDING OUTLETS AND ACTIONS TO THE HEADER
51
4.4
Adding Outlets and Actions to the Header
Right now the "Back" button is connected to the web view's goBack:
method. Let's insert the SimpleBrowserController in between. We're going
to need a method in the controller that we use for loading the previous
web page. It will be called by the "Back" button and will, in turn, have
to send the message goBack: to the web view. This means we'll need an
Outlet for the web view and an Action for the method.
Back in Xcode add an IBAction named loadPreviousPage: to SimpleBrowser-
Controller.h. You'll also need to add an IBOutlet named myWebView that is
a pointer to a WebView object.
Download CreatingAController/SimpleBrowserWithController/partial/SimpleBrowserController2.h
#import <Cocoa/Cocoa.h>
@interface SimpleBrowserController : NSObject {
IBOutlet WebView * myWebView;
}
-(IBAction) loadPreviousPage: (id) sender;
@end
Before you hit Build & Go, can you see a problem? If you can, fix it. If
not, I'll explain how in a minute. Hit Build & Go and you should get the
message "Build failed (1 error, 2 warnings)"
Let's deal with the error first. Your IBOutlet uses the WebView class. But
your program doesn't know anything about the WebView class. You have
to import the appropriate header file. Back in Section 3.3, Reading the
Docs, on page 36 you saw that WebView is part of the WebKit framework
and is declared in WebView.h. Add this import to SimpleBrowserController.h
below the line importing the Cocoa headers.
Download CreatingAController/SimpleBrowserWithController/partial/SimpleBrowserController3.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebView.h>
@interface SimpleBrowserController : NSObject {
IBOutlet WebView * myWebView;
}
-(IBAction) loadPreviousPage: (id) sender;
@end
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
WIRING UP THE CONTROLLER
52
Figure 4.4: The Outlets and Actions appear in IB
Build & Go again. This time the build will succeed. There will be two
warnings because your header file promised there would be a method
named loadPreviousPage and you haven't implemented it yet. Even with
this unfinished work, the program builds and launches and works the
same way it did before. Stop the running application by using the red
stop sign labeled "Tasks".
4.5
Wiring up the Controller
Go back to Interface Builder and in the Document window click on the
Simple Browser Controller object and open the Connections Inspector.
Isn't that cool? You should see something like Figure 4.4. Under "Out-
lets" you now find myWebView and under "Received Actions" you see
loadPreviousPage:. I know it's supposed to work that way, but I love see-
ing the Outlets and Actions you added to the header file show up in
IB.
This also highlights, one last time, the role of the header file. Remember
that the header file is the public face of the class. It tells other classes
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
IMPLEMENTING THE LOADING OF THE PREVIOUS PAGE
53
Figure 4.5: Connecting the Outlets and Actions
and object what they can call on the class to do. You see this in the
Interface Builder representation.
Drag from the circle to the right of loadPreviousPage: to the "Back" button
to make that connection. Also drag from the circle to the right of myWe-
bView to the web view. Your connections should look like Figure 4.5.
Save your work and Build & Go. Type in a few URLs. Try out the "Back"
button. Nothing should happen. Actually, what should happen is that
when you press the "Back" button the loadPreviousPage: message is sent
to the SimpleBrowserController object. But we haven't implemented the
loadPreviousPage: method so nothing happens.
4.6
Implementing the Loading of the Previous Page
There's not much code to write. When the "Back" button sends the
loadPreviousPage: message to the SimpleBrowserController object, the Sim-
pleBrowserController object just turns around and sends the goBack mes-
sage to myWebView.
Download CreatingAController/SimpleBrowserWithController/partial/SimpleBrowserController4.m
#import "SimpleBrowserController.h"
@implementation SimpleBrowserController
-(IBAction) loadPreviousPage:(id) sender {
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: FINISH THE CONTROLLER
54
[myWebView goBack];
}
@end
That's it! Build & Go and you have a working "Back" button again.
4.7
Exercise: Finish the controller
Add another action to SimpleBrowserController named loadNextPage: and
use it to make the "Forward" button work through the controller. (Hint:
you can find the name of the WebView action to call using IB)
Once that is working, add one last Action to SimpleBrowserController.
Name it loadURLFrom: and use it to make the URL entry from the text
field work through the controller. Remember that the web view is going
to need to send a message back to the textField to get its string value.
You can do this in more than one way. You might be tempted to intro-
duce an outlet for the text field. In this case you don't have to. Use the
sender to talk back to the text field.
4.8
Solution: Finish the controller
The first half of the exercise mirrors the step we took together. You'll
need to make three changes.
First you need to add an action to the header file SimpleBrowserController.h
using Xcode and save it.
-(IBAction) loadNextPage: (id) sender;
Second, go back to Interface Builder and select the SimpleBrowserCon-
troller. In the Connections Inspector drag from the circle to the right of
loadNextPage: to the "Forward" button to make the connection. Save
your work.
Finally, you need to implement the method. Looking at Web View's con-
nections you can see that the method you need to call is goForward:.
Back in Xcode, modify SimpleBrowserController.m to add this method.
Download CreatingAController/SimpleBrowserWithController/partial/SimpleBrowserController5.m
-(IBAction) loadNextPage:(id) sender {
[myWebView goForward];
}
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
DISABLING AND ENABLING THE BUTTONS
55
Save your work. Build & Go and you should find that you now have
working back and forward buttons. Enter a few URLs and you should
be able to use the buttons to move back and forward through your list.
For the second half of the exercise we'll follow three similar steps. First,
in Xcode add an action named loadURLFrom: to the header file and save
it.
-(IBAction) loadURLFrom: (id) sender;
Next, in Interface Builder and select the Connections Inspector for the
SimpleBrowserController. Drag from the circle to the right of loadURLFrom:
to the textfield to make the connection. We've already configured the
text field to send an action to its target when the user hits the enter
key. You can check that the Action value is still set this way using the
Attributes Inspector for the text field. Save your work.
Back in Xcode, implement your method like this.
Download CreatingAController/SimpleBrowserWithController/partial/SimpleBrowserController5.m
-(IBAction) loadURLFrom: (id) sender {
[myWebView takeStringURLFrom:sender];
}
There's some coding magic going on here. You know that the sender is
an NSTextField so you know that you can pass it in as the argument for
takeStringURLFrom:. At compile time, sender has the generic type id so we
can't tell just by looking at the code that this will run right. If you are
coming from a strong-typed language this may take some getting used
to.
4.9
Disabling and Enabling the Buttons
There are still some fundamental things wrong with our web browser
from a usability standpoint. For example, one problem is that the but-
tons are enabled all the time. This implies that the user can press them
at any time. As Obj-C programmers we know that this is fine. We can
send the goBack method to the web view as often as we like. If there is
no previous page to load then it won't bother trying.
But one thing that distinguishes Cocoa programming is that we need
to look at our application from the end users point of view. If there's
no point in pushing a button then there should be a visual cue that
let's us know that. In this section you will write the code to enable and
disable the buttons. It will mostly work.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
DISABLING AND ENABLING THE BUTTONS
56
Figure 4.6: The Button Connections
You should complete the exercise before reading on as the solution is
included with our code below.
We'll need to send messages to the two buttons so add two Outlets to
your header file.
Download CreatingAController/SimpleBrowserWithController/SimpleBrowserController.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebView.h>
@interface SimpleBrowserController : NSObject {
IBOutlet WebView * myWebView;
IBOutlet NSButton * backButton;
IBOutlet NSButton * forwardButton;
}
-(IBAction) loadPreviousPage: (id) sender;
-(IBAction) loadNextPage: (id) sender;
-(IBAction) loadURLFrom: (id) sender;
@end
Save the header file and move back to Interface Builder's Document
window. Select the Simple Browser Controller and open the Connections
Inspector. You should have new Outlets labeled backButton and forward-
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
DISABLING AND ENABLING THE BUTTONS
57
Button. Connect them to their buttons and your inspector should look
like Figure 4.6, on the previous page.
Before you move on click on the "Back" button and open the Attributes
Inspector (click the left-most icon at the top of the inspector window).
Look most of the way down to find the "Enabled" checkbox in the Con-
trol group. Uncheck the check box. Do the same for the "Forward" but-
ton. Save your work. Now your browser will start up with the two but-
tons disabled.
Before you write the code to reset the buttons, let's think about what
you want it to do. For the "Back" button you want to set it to be enabled
or not enabled based on whether the web view can go back or not.
Similarly, the state of the "Forward" button will depend whether the
web view can go forward or not. A quick look at the docs for NSButton
and WebView shows us the canGoBack and canGoForward methods we
can use to reset the buttons. Here's how we'd do it.
- (void) resetButtons {
[backButton setEnabled:[myWebView canGoBack]];
[forwardButton setEnabled:[myWebView canGoForward]];
}
So how do you call the resetButtons method from other methods in Sim-
pleBrowserController? You send a message to self.
[self resetButtons];
We don't declare resetButtons in the header file because we aren't encour-
aging other object to send a message to our controller to reset the but-
tons. To keep the compiler happy, we move resetButtons to the top of the
implementation so the other methods know about it.
Download CreatingAController/SimpleBrowserWithController/SimpleBrowserController.m
#import "SimpleBrowserController.h"
@implementation SimpleBrowserController
- (void) resetButtons {
[backButton setEnabled:[myWebView canGoBack]];
[forwardButton setEnabled:[myWebView canGoForward]];
}
-(IBAction) loadPreviousPage:(id) sender {
[myWebView goBack];
[self resetButtons];
}
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
STILL NEEDS WORK
58
-(IBAction) loadNextPage:(id) sender {
[myWebView goForward];
[self resetButtons];
}
-(IBAction) loadURLFrom: (id) sender {
[myWebView takeStringURLFrom:sender];
[self resetButtons];
}
@end
Everything looks good. Save your work, build the application, and try it
out.
4.10
Still needs work
Uh oh. The buttons now work worse than they did before. Before we
made these latest changes the buttons were always enabled. The prob-
lem with that was that the user could sometimes push the buttons
without anything happening. Now the opposite is true. Sometimes the
buttons aren't enabled when they should be. There are two related
problems.
To see one problem, launch the application and enter a URL. Once the
page loads enter another URL. The "Back" button should be enabled but
it isn't. Once the second page loads enter a third URL. Now the "Back"
button is enabled and you can use it to navigate all the way back to the
first page. The problem is that it takes a while for the URL to load and
you have already set the state of the buttons based on the status just
after the request to load the URL was made. It would be better if you
checked canGoBack: and canGoForward: after the URL starts to load.
Quit the application and launch it again. Enter a URL. Once the page
loads click on a link. Once that page loads follow another link. As long
as you continue to use the links in the web view the buttons are never
enabled. The history is being maintained but there's no callback to
resetButtons. You can see this if you enter a URL. Once the page loads
you can use the buttons to move back and forward through the history.
All of the methods we've seen so far execute immediately. What we need
is a way of delaying when a message is sent. Don't ask me if I can go
back or forward until I have loaded the page you requested. Fortu-
nately, this notification mechanism is built into Cocoa with the notion
of Delegates. We'll look into how they work in the next chapter.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
Chapter 5
Listening to Delegates and
Notifications
Stuff happens.
In fact, lots of stuff is happening as your end-user enters URLs and
presses buttons and clicks on links in your SimpleBrowser. The most
basic type of event is Target/Action. The user presses a button and an
action is sent to a target. You've learned two ways of working with these
events: you can use Interface Builder to directly wire the object sending
the message to the target object that will perform the action or you can
create a controller.
But once you enter a URL or use the "Back" or "Forward" buttons addi-
tional events and messages are flying by that you don't even see. For
example, when you load a page in Safari you've probably noticed that
the title of the new page is displayed above your toolbar before the page
actually loads. Meanwhile, you can see the progress of the page being
loaded by the blue bar that works its way across the text field con-
taining your URL. There are messages being sent that Safari is able to
capture and respond to. What about us?
In this chapter you'll see that there are plenty of messages zipping by us
all the time and you'll learn to listen for them and respond to them. We'll
display the title of the web page we're loading and create a progress bar
to demonstrate two mechanisms of capturing and responding to these
otherwise invisible events. Delegates will let us customize behavior for
a class without creating a subclass. Notifications let us (and anyone
else) listen for updates that we might want to respond to. We'll use a
Prepared exclusively for Jesper Noehr
DELEGATES
60
delegate to display a page's title and we'll use a notification to drive our
progress indicator.
5.1
Delegates
We've been working with buttons for a few chapters. Every button needs
to know what happens when it is clicked. In other words, part of being
a button is having a target and an action to send that target when the
button is pressed. You don't subclass a button to assign its target and
action.
On the other hand, if you're implementing an application that is going
to display one or more web views, there's no clear way of responding
to a request to close a web view. Think of what happens when you use
Safari. If you have one web view open and hit D W then the browser
window closes. If instead you are using tabbed browsing and have mul-
tiple tabs open then D W closes the active tab. The window isn't closed.
You could probably come up applications that would benefit from a
whole range of behaviors on D W.
So Apple provides you with default behavior. If your user closes a web
view then it sends a webViewClose: message. The default is that a close
message is sent to the NSWindow containing the WebView sending the
original message.
If you would like to change this default behavior your first impulse may
be to create a subclass and override this message. Delegation means
that you don't have to. Instead, you assign some class as the delegate
for handling a collection of Web UI events. These are UI actions that the
WebView class designers anticipate that you might want to customize.
In your delegate class you just implement the methods whose behavior
you want to change and leave the other ones alone.
These four delegates are listed in the overview section of the WebView
class reference:
· WebFrameLoadDelegate,
· WebPolicyDelegate,
· WebResourceLoadDelegate, and
· WebUIDelegate.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SETTING THE WINDOW TITLE
61
Figure 5.1: Connecting to the Delegate
These are actually protocols and not classes. They specify the method
signatures that can be used by a class implementing the protocol. This
ensures that when a message is sent to the delegate object the corre-
sponding method will be called. This also means that the compiler can
let you know if there's a problem.
Look in the "Tasks" section of the WebView docs and you will see ten
methods for assigning and working with delegates. You can set dele-
gates using outlets in Interface Builder or you can set them programat-
ically using a method like setUIDelegate:.
5.2
Setting the Window Title
In this section we'll update the window's title bar based on the title of
the page you're loading. We'll use the webView:didReceiveTitle:forFrame:
method in the WebFrameLoadDelegate.
Start by connecting the delegate to our controller in Interface Builder
as shown in Figure 5.1. Select the web view and open the Connections
Inspector. You should see the four delegate methods mixed in with the
other outlets. Drag to connect frameLoadDelegate to your SimpleBrowser-
Controller. Save your work.
While you're still in Interface Builder you may as well change the name
of the window from "Window" to "Simple Browser". Click on the Window
and then the Attributes Inspector. Type "Simple Browser" in the Title
field.
Save your work. We'll head back to Xcode to finish up.
The webView:didReceiveTitle:forFrame: method is called when the title is
available. You can see that the message we receive also includes the
title as a parameter. Here's how we'll use it.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SETTING THE WINDOW TITLE
62
· We don't need to add anything to the header file. All fifteen of
the methods listed in the WebFrameLoadDelegate protocol are now
available to us in SimpleBrowserController.
· We need to implement webView:didReceiveTitle:forFrame: in Simple-
BrowserController.m by filling in the body of this method:
-(void)webView:(WebView *)sender didReceiveTitle:(NSString *)title
forFrame:(WebFrame *)frame{
}
We copied the signature of the method from the WebFrameLoadDel-
egate protocol documentation.
· We wait. The webView:didReceiveTitle:forFrame: method gets called
once the title for the URL being loaded is available.
When this method is invoked you want to set the title attribute of the
application window to the title that is returned to you. Looking at the
method signature you can see that when the method is called you will
be passed a handle to the web view you are using and to the title of
the new page. You can use the web view to get a pointer to the window
containing the web view like this:
[sender window];
Then you just ask this window to set it's title to the title that you get
when this delegate method is called. You need to add this method to
SimpleBrowserController.m
Download DelegatesAndNotification/SimpleBrowserFinal/partial/SimpleBrowserController1.m
- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title
forFrame:(WebFrame *)frame {
[[sender window] setTitle:title];
}
Save your work. Build & Go and now no matter how you navigate to a
web site in your browser, the title will appear as soon as it is available.
All you had to do was find the right method, create and configure the
outlets and implement the method with a single line of code:
[[sender window] setTitle:title];
Again, we did not list the webView:didReceiveTitle:forFrame: method in
the SimpleBrowserController header. This method is not part of the pub-
lic interface for SimpleBrowserController. The only object that needs to
know that SimpleBrowserController implements this method is the dele-
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: UPDATING THE URL AND SETTING BUTTONS
63
gating object myWebView. The message will only be sent if the delegate
is assigned and the method is implemented.
5.3
Exercise: Updating the URL and Setting Buttons
Now that you've seen how to display the page title as the window's title
when it's available, you can reset the buttons and update the URL once
the page is loaded. Until now, if the user clicks on a link in a web page,
the new page will load but the URL won't change in the text field as
the user would expect from experience with pretty much every other
browser.
To reset the buttons and update the URL after the page is loaded follow
these steps:
1. Identify a method in the WebFrameLoadDelegate protocol that is
called when the frame has completed loading.
2. Add an outlet that you will use to send messages to the NSTextField
that is used to enter the URL.
3. Connect this outlet to the text field in Interface Builder.
4. Implement the delegate method to do two things: (a) set the text
field's string value to be the URL for the page's main (and only)
frame and (b) call resetButtons.
Hint 1: If you look in the NSTextField Class reference for a method to set
the text field's string value, you won't find what you are looking for.
Unlike the documentation for other frameworks, the inherited methods
do not appear in the Cocoa docs. So you need to look at the class refer-
ence for NSTextField's super class NSControl. You'll find the setStringValue:
in Tasks under "Setting the Control's Value."
Hint 2: WebView has a lot of methods that it's easy to miss the one you
want for fetching the right URL. Look in the "Overview" section under
the heading "Getting and Setting Frame Contents".
5.4
Solution:Updating the URL and Setting Buttons
You're going to implement the webView:didFinishLoadForFrame method.
You will reset the buttons and update the URL in the text field. You
already have outlets for the buttons so you just need to create an outlet
for the text field. In Xcode add
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SOLUTION:UPDATING THE URL AND SETTING BUTTONS
64
Completing your thought
You can adjust Xcode's Code Sense settings in the preferences.
You can adjust how quickly code completions appear. Instead
of having to look up the entire hierarchy for the method you
want you can choose Edit > Completion List to see all of the avail-
able completions. In the example in the exercise, if you do that
after typing [inputField you would have seen setStringValue: in the
list.
IBOutlet NSTextField * inputField;
to SimpleBrowserController.h along with the other outlets. Save the file.
In Interface Builder click on the SimpleBrowserController in your Docu-
ment window. Use the Connections Inspector to connect the inputField
to the text field you use to input URLs. Save your work.
Back in Xcode, implement your method like this.
Download DelegatesAndNotification/SimpleBrowserFinal/partial/SimpleBrowserController2.m
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
[inputField setStringValue:[sender mainFrameURL]];
[self resetButtons:sender];
}
You aren't done yet. Sure, if you build and run your code it will work
perfectly but you have some waste to take care of. Look at the last line
of each of these methods.
Download DelegatesAndNotification/SimpleBrowserFinal/partial/SimpleBrowserController2.m
-(IBAction) loadPreviousPage:(id) sender {
[myWebView goBack];
[self resetButtons];
}
-(IBAction) loadNextPage:(id) sender {
[myWebView goForward];
[self resetButtons];
}
-(IBAction) loadURLFrom: (id) sender {
[myWebView takeStringURLFrom:sender];
[self resetButtons];
}
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CLEANING UP
65
There's no need to call resetButtons in any of these methods as it's being
called when the frame is done loading. Remove those three lines and
re-run your application. You'll find that it still runs fine.
5.5
Cleaning up
Now that you have removed [self resetButtons]; from loadPreviousPage:,
loadNextPage:, and loadURLFrom:, the methods don't do very much. There
really isn't a need to use the controller for these actions any more.
Clean up your code and interface while you are thinking about it.
In Interface Builder click on the SimpleBrowserController in the Document
window and select the Connections Inspector. Disconnect the three
Received Actions by clicking on the "X"s. Now click on the web view
and use the Connections Inspector to reconnect the three actions you
had before. Drag from goBack: to the "Back" button, from goForward: to
the "Forward" button, and from takeStringURLFrom: to the text field. Save
your work in Interface Builder and then choose Build & Go in Xcode.
Your application runs perfectly.
That should worry you a bit.
You have just redirected your control flow entirely from IB. You have
three methods in Xcode that are no longer being called. This is some-
thing you need to remember when you are working with Cocoa pro-
grams. You can't figure out the whole story merely by reading through
the code. You have to look at the connections you have created else-
where.
But, you should also make it easier for people (including yourself) who
will come back to this project and try to figure out what is going on.
Since these three methods are not called anymore you should eliminate
them from both the header and implementation files.
Once you've removed these, I think you can make one more cut in this
cleaning pass. There real isn't any need for our controller to know about
the web view. The only dependency is in the resetButtons method.
Download DelegatesAndNotification/SimpleBrowserFinal/partial/SimpleBrowserController3.m
- (void) resetButtons {
[backButton setEnabled:[myWebView canGoBack]];
[forwardButton setEnabled:[myWebView canGoForward]];
}
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CLEANING UP
66
But now the resetButtons method is called from inside another method
that has a handle to the web view. Change the method to accept an
argument like you see on line 5 in this listing.
Download DelegatesAndNotification/SimpleBrowserFinal/partial/SimpleBrowserController4.m
Line 1
#import "SimpleBrowserController.h"
-
-
@implementation SimpleBrowserController
-
5
- (void) resetButtons:(WebView *)theWebView {
-
[backButton setEnabled:[theWebView canGoBack]];
-
[forwardButton setEnabled:[theWebView canGoForward]];
-
}
-
10
- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title
-
forFrame:(WebFrame *)frame {
-
[[sender window] setTitle:title];
-
}
-
15
- (void)webView:(WebView *)sender
-
didFinishLoadForFrame:(WebFrame *)frame {
-
[inputField setStringValue:[sender mainFrameURL]];
-
[self resetButtons:sender];
-
}
20
-
@end
Note that you also have to change the message you send to reset the
buttons to pass along the sender as you see in the listing in line 18.
Finish your cleanup by breaking the connection between SimpleBrowser-
Controller's web view outlet and your web view in Interface Builder. Also,
remove the web view outlet declaration from your header file in Xcode.
Save your work. Your header file should now look like this.
Download DelegatesAndNotification/SimpleBrowserFinal/SimpleBrowserController.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebView.h>
@interface SimpleBrowserController : NSObject {
IBOutlet NSButton * backButton;
IBOutlet NSButton * forwardButton;
IBOutlet NSTextField * inputField;
}
@end
Before we move on to notifications, take a minute to look at how little
code we needed when we worked with the existing Apple frameworks.
Whenever your code gets long and complicated you should pause and
consider whether there's an easier way to accomplish what you are try-
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
NOTIFICATIONS
67
ing to do. Your goal is to write simple code that is clear and understand-
able. Cocoa programmers do not value code that is short and clever to
the point of being obscure.
5.6
Notifications
Think of it this way. A delegate gives the delegating object their phone
number and says "call me--and only me--if this particular event hap-
pens." The delegating object agrees and gives the delegate the secret
code it will use. The delegating object walks around until one of the
triggering actions happens and then takes out the slip of paper with
the phone number and the code and dials it and says "the web view
has closed," and hangs up.
A notification works more like an RSS feed. Zero, or one, or many
objects might register to receive the feed in their particular area of inter-
est. One or more objects might be able to post to the feed. There may be
long periods where nothing is posted to the feed. No matter. The objects
stay subscribed. Then one day something is posted to the feed and all
of the objects get the news.1
An object registers with a notification server for each type of notification
it wants to receive. When it receives a notification, a method will be
called that the receiving object specifies in its registration. We'll talk
about where to put your registration in a minute but it should look
something like this.
[[NSNotificationCenter defaultCenter]
addObserver:observerObject
selector:@selector(methodName:)
name:NameOfNotification
object:notifyingObject];
Let's take a closer look at this template code.
· You are sending the addObserver:selector:name:object: method to
the default notification center.
· Here the observer is set to self but it can be any object interested in
receiving the notification.
1.
Yes, I know it's not exactly like a feed. This is a metaphor that calls out the difference
between the one to one relationship of the delegate and the possibly many to many rela-
tionship of notification. It also highlights that in the case of notification there is a central
service responsible for publishing the notifications.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
AWAKE FROM NIB
68
· The selector is the name of the method belonging to the observer
that will be called when the notification is received.
· The notification object is passed in as the method's single argu-
ment.
· The name is the name of the notification being registered for.
· As for the object selector: this is the object whose notifications
you are interested in. For example, you may have more than one
web view that is posting notifications and you might only want to
monitor a particular one. You can also pass in nil to receive this
type of notification posted by any object in your application.
We're going to use notifications to implement a simple progress indi-
cator that gives the user some feedback on how much of the page
has loaded. The WebView class reference lists eight available notifi-
cations. Three of them are related to information on the progress of
loading the page: WebViewProgressEstimateChangedNotification, WebView-
ProgressStartedNotification, and WebViewProgressEndedNotification. All you
have to do is register for notifications and then implement the method
that is called when a notification is received. We'll use the method esti-
matedProgress defined in WebView to get a double between 0.0 and 1.0
that indicates the proportion of the page that's been loaded.
5.7
Awake from Nib
Generally, we want to register for notification automatically when the
application starts but before the user has a chance to take any actions.2
Because you added your two controller objects to your nib, they are part
of the object graph that is reconstructed when the application starts up.
We have not yet created objects with code but the basic pattern looks
something like this.
[[Classname alloc] init]
There are variations on the init method name. Objects created in Inter-
face Builder may call initWithCoder: or initWithFrame: instead when they
are unarchived.
Once the objects are created, the actions and outlet connections are
formed.
2.
In this case, we could wait until the user enters the first URL.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
ADDING A PROGRESS INDICATOR
69
Figure 5.2: Placing the Progress Indicator
Next, before anything is displayed to the end user, an awakeFromNib
message is sent to all objects that have this method.3 Just add this
method to any file that needs to perform tasks just after initialization.
-(void) awakeFromNib {
}
This is where you'll put your event notification and anything else that
needs to happen after your objects are instantiated and knitted together
but before the user is given control. You are not guaranteed the order
in which objects will get the awakeFromNib.
5.8
Adding a Progress Indicator
We'll bounce back and forth between Xcode and Interface Builder to
create and animate our progress bar.
3.
No message is sent to objects that don't have this message so that you don't get a
runtime error.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
ADDING A PROGRESS INDICATOR
70
Figure 5.3: Configuring the Progress Indicator
In Xcode, create a new Cocoa > Objective-C class and name it LoadIndi-
catorController. Check the boxes to generate a header file and to add it
to your target. The controller will need to connect to the progress bar
to update the display and it will need to connect to the web view just
for a moment to set up the notification. We could listen to notifications
coming from any object--but we only want to respond to updates from
out web view. So your header file should look like this.
Download DelegatesAndNotification/SimpleBrowserFinal/LoadIndicatorController.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebView.h>
@interface LoadIndicatorController : NSObject {
IBOutlet NSProgressIndicator * loadIndicator;
IBOutlet WebView * myWebView;
}
@end
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SPELLING COUNTS
71
In Interface Builder search the Library for "progress" and you will find
a couple of examples of NSProgressIndicators. Unfortunately, we don't see
exactly what we want but we can modify the "Indeterminate Progress
Indicator" so it behaves the way we want it to. Drag it into the bottom
of the window and position it below the web view as in Figure 5.2, on
page 69
Configure the progress bar as shown in Figure 5.3, on the preceding
page. Make sure you uncheck the "Indeterminate" check box. Set the
values to the range returned by the estimatedProgress and set the initial
value at 0.0.
Next, you'll need to create an object that represents the LoadIndicator-
Controller. Grab an NSObject from the Library and drag it over to your
Document window. Select your new object and open the Identity Inspec-
tor and enter "LoadIndicatorController" for the class name. Switch to
the Connections Inspector and you will see the outlets matching the
two from your header file. Connect the loadIndicator to the progress bar
and myWebView to the web view. Save your work.
5.9
Spelling Counts
One of the problems with dynamic binding is the compiler can't help
find your typos. Let's see this problem in action.
Find this code in SimpleBrowserController.m.
- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title
forFrame:(WebFrame *)frame {
[[sender window] setTitle:title];
}
This method is called when the title is available. Let's introduce an obvi-
ous typo in the name of the method. Change webView to pragWebView.
- (void)pragWebView:(WebView *)sender didReceiveTitle:(NSString *)title
forFrame:(WebFrame *)frame {
[[sender window] setTitle:title];
}
Build & Go. Everything should work fine except the title bar. As you
load different pages you won't see the title of the page in the window's
title bar. What's happening? The WebView looks to see which delegate
methods can be called and because of the misspelling SimpleBrowserCon-
troller doesn't implement any of the methods in the WebFrameLoadDel-
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
IMPLEMENTING THE LOAD INDICATOR
72
egate protocol. Nothing calls the pragWebView:didReceiveTitle:forFrame:
method so the title is never changed.
Before we forget, change pragWebView: back to webView:. Save your
work. Build & Go and check that everything works again.
There are two very common typing mistakes that are difficult to detect
when you are searching through your code for your mistake. The meth-
ods goBack and goBack: are completely different methods even though
they almost the same. It is too easy to drop the ":" and end up with a
method that is never called. Also, case matters in Obj-C. It is easy to
use the wrong case in a method call and again end up with a hard to
detect runtime error.
There are two other places where it's easy to introduce an error while
working with notifications. The first place is that the notification name
is passed in as an NSString. You've probably seen sample code where this
is actually passed in as a string literal. It is better to use a variable so
that the compiler can signal that you're trying to listen to a notification
that isn't defined. In the code in the next section you'll see that we do
just that.
The other place is that we pass in the method name that will be called
when the notification is received as an argument to the @selector( ) direc-
tive. A selector is effectively a pointer to a method.4 You create one with
the syntax @selector(method_name).
5.10
Implementing the Load Indicator
Head back to Xcode and write the implementation. First you will regis-
ter for the WebViewProgressEstimateChangedNotification from the body of
the awakeFromNib method in your LoadIndicatorController. Register self as
the observer, use the method name updateLoadIndicator: as the selector,
and use myWebView as the notifying object.
The updateLoadIndicator: should check on the current progress estimate
from myWebView and set the progress indicator to that value. Here's
your implementation file.
4.
"Effectively" is a word we authors use when we aren't going to explain what's really
going on. The details are actually pretty cool but are beyond the scope of this book. For
more information see Apple's publication "The Objective-C 2.0 Language."
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE:ENHANCING THE PROGRESS INDICATOR
73
Download DelegatesAndNotification/SimpleBrowserFinal/partial/LoadIndicatorController1.m
#import "LoadIndicatorController.h"
@implementation LoadIndicatorController
- (void) updateLoadIndicator: (NSNotification *) notification {
[loadIndicator setDoubleValue: [myWebView estimatedProgress]];
}
-(void) awakeFromNib {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(updateLoadIndicator:)
name:WebViewProgressEstimateChangedNotification
object:myWebView];
}
@end
Notice the colon at the end of the name of the selector. Build & Go and
load a page. Your new progress indicator should update as the page
loads. When the page is done loading the progress indicator remains
visible, completely filled below the web view. There's nothing wrong with
that, but it might be nice if the progress bar was only visible when the
page is actively loading.
5.11
Exercise:Enhancing the Progress Indicator
Take a few minutes and make the progress indicator appear when the
page begins loading and vanish when the page is done loading. You
should start by using IB to check the progress indicator as "Hidden".
You can then programatically set the progress indicator to not be hid-
den and to have an initial value of 0.0 when you receive a notifica-
tion that the progress has started. You can also programatically set the
progress indicator back to hidden when you receive notification that
the progress has finished.
5.12
Solution:Enhancing the Progress Indicator
It turns out that it takes more code to implement the Progress Indica-
tor than the rest of the browser. Here is my take on LoadIndicatorCon-
troller.m.
Download DelegatesAndNotification/SimpleBrowserFinal/LoadIndicatorController.m
#import "LoadIndicatorController.h"
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SOLUTION:ENHANCING THE PROGRESS INDICATOR
74
@implementation LoadIndicatorController
- (void) updateLoadIndicator: (NSNotification *) notification {
[loadIndicator setDoubleValue: [myWebView estimatedProgress]];
}
- (void) clearLoadIndicator: (NSNotification *) notification {
[loadIndicator setHidden:YES];
}
- (void) startLoadIndicator: (NSNotification *) notification {
[loadIndicator setDoubleValue:0.0];
[loadIndicator setHidden:NO];
}
-(void) awakeFromNib {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(updateLoadIndicator:)
name:WebViewProgressEstimateChangedNotification
object:myWebView];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(startLoadIndicator:)
name:WebViewProgressStartedNotification
object:myWebView];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(clearLoadIndicator:)
name:WebViewProgressFinishedNotification
object:myWebView];
}
@end
There are, of course, many other choices we could have made. We could
have put this code in the SimpleBrowserController class. If we'd put it there
we could have used the delegate methods to signal when the frame
load is beginning and ending and kept the notification for the ongoing
estimate updates.
In any case, it's time to say goodbye to our SimpleBrowser. We've used
it to cover a lot in these first few chapters.
· You learned how to create a user interface and set the targets and
actions with Interface Builder.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SOLUTION:ENHANCING THE PROGRESS INDICATOR
75
· You learned the basics of Objective-C and how to read and write
methods, messages, interfaces, and implementations.
· You created a controller and learned how to connect it to the inter-
face.
· You learned how to use delegates and notifications as well as how
to use id, awakeFromNib, and selectors.
One thing you haven't done yet is create an object outside of Interface
Builder. In the next chapter we'll look at objects and properties and
create an object in code. Along the way we'll touch on memory manage-
ment and garbage collection.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
Chapter 6
Obj-C Insights: Objects and
Properties
So you've built a working web browser. On the one hand, you're feeling
pretty good about yourself. You just dragged and dropped and wrote a
few lines of code and had yourself a web browser. That's power.
On the other hand, it doesn't feel like real programming. A real pro-
grammer wouldn't use a bunch of GUI tools. Real programmers would
build the objects with their bare hands. They would say "Arrrr" and
make other pirate sounds while they wrestle with memory manage-
ment. You aren't going soft on me are you? Come to think of it, you
haven't even built yourself a "Hello World" program yet.
In this chapter we'll fix all that. We'll start with a fairly basic "Hello
World" and add objects and properties to it. Along the way we'll look at
some basic memory issues.
6.1
Hello, World
Let's create our "Hello, World" project in Xcode. Remember in Xcode you
create a new project with File > New Project or B D N. Choose the Appli-
cation > Cocoa Application template and name the project "HelloWorld".
Build & Go and after a pause an empty window will appear. The appli-
cation works, it just doesn't do anything. Quit HelloWorld or stop the
tasks from within Xcode.
Prepared exclusively for Jesper Noehr
CREATING A NEW CLASS
77
For now we're going to ignore the GUI and write directly to the console
using NSLog( ).1 You will pass in an NSString to NSLog( ) to be printed to
the console window. Instead of just enclosing the string to be printed
in quotes, you need to indicate an NSString like this:
NSLog(@"Hello, world" );
In other words you need to put the @ before the open quote. Add that
line to main.m like it is here.
Download ObjectsAndProperties/HelloWorld/partial/main1.m
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
NSLog(@"Hello, world" );
return NSApplicationMain(argc,
(const char **) argv);
}
While we are reading results in the console window you'll find it easier
if you stick to this cycle.
· Clear the Console with Run > Clear Console or C E D R.
· Show the Console with Run > Console or B DR.
· Build & Go (or use the keyboard shortcut for this E D F).
HelloWorld should start up, the empty window will appear and the con-
sole should contain a line indicating the time at which the session
started followed by a line with a time stamp and this:
HelloWorld[19673:10b] Hello, world.
Once you've admired your achievements for a bit you can quit Hel-
loWorld and get back to work.
6.2
Creating a new Class
If our only goal was to print "Hello, world" to the console we'd be done.
But we're going to unnecessarily complicate the example for the remain-
der of the chapter to quickly introduce classes, objects, memory, and
properties.
1.
As before, you may see something slightly different than what I see. I'll report on what
I see in my console window.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING A NEW CLASS
78
Create a pair of files for a new class using File > New File ... or DN. Choose
Cocoa > Objective-C class and press "Next". Name the file Greeter.m and
make sure the checkbox is checked to generate Greeter.h as well. 2
In this iteration of "Hello, world" we're going to define a new class but
not create any objects from it.. It sounds a bit confusing and we'll sel-
dom do this in real life but you will actually use class methods all the
time to create objects so in this section we'll create and use our own
class method.
The Greeter class will have the class method sayHello. We can call this
method without instantiating Greeter. The downside is that sayHello
won't have access to any of the instance variables for the instance we
are calling from.
Declare your method in Greeter.h. Notice that this time there is a +
in front of the method declaration to indicate that sayHello is a class
method. In the past we've used the - in front to indicate a method
belonging to the instance of the class.
Download ObjectsAndProperties/HelloWorld/partial/Greeter2.h
#import <Cocoa/Cocoa.h>
@interface Greeter : NSObject {
}
+ (void) sayHello;
@end
Remember when you created your header files for your controllers you
put the declarations for your outlets inside the braces and the dec-
larations for your actions between the closing brace and @end. The
same is true here. You will declare your instance variables between the
braces and declare both your class methods and your instance methods
between the closing brace and @end.
More concretely, here is your header file for the Greeter.
The method does nothing but print our message to the console. Here's
the implementation file.
Download ObjectsAndProperties/HelloWorld/partial/Greeter2.m
#import "Greeter.h"
2.
From now on I'll assume you are creating the header file whenever you create a new
implementation file.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING A NEW OBJECT (IRRESPONSIBLY)
79
@implementation Greeter
+ (void) sayHello {
NSLog(@"Hello, world." );
}
@end
You need to make two changes to main.m. You have to import the header
file for Greeter so that the compiler doesn't complain when you try to
call sayHello. You also have to send the message sayHello to the class
Greeter.
Download ObjectsAndProperties/HelloWorld/partial/main2.m
#import <Cocoa/Cocoa.h>
#import "Greeter.h"
int main(int argc, char *argv[])
{
[Greeter sayHello];
return NSApplicationMain(argc,
(const char **) argv);
}
Follow the steps to clear the console, bring up the console, and build
and run your application and the results should be exactly the same
as before. When you're ready, quit the application and let's change this
application to create objects.
6.3
Creating a new Object (irresponsibly)
So here we are in a pedagogical bind.
I want to show you how to create a new object and call an instance
method. But there's a lot of things you should know first if you want to
act responsibly. But if I show you how to take care of memory first your
eyes will roll back in your head and you'll stop listening to me.
My solution is to not behave responsibly. Let's throw our caution to the
wind and make three changes to the code to create an object of type
Greeter.
First, change the header file Greeter.h to change the + to a - to declare
sayHello as an instance method instead of a class method.
Download ObjectsAndProperties/HelloWorld/partial/Greeter3.h
#import <Cocoa/Cocoa.h>
@interface Greeter : NSObject {
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING A NEW OBJECT (IRRESPONSIBLY)
80
}
- (void) sayHello;
@end
Make the corresponding change of + to - in the implementation file
Greeter.m or your compiler will complain that you declared an instance
method that you didn't implement (it will not complain that you imple-
mented a class method that you didn't declare).
Download ObjectsAndProperties/HelloWorld/partial/Greeter3.m
#import "Greeter.h"
@implementation Greeter
- (void) sayHello {
NSLog(@"Hello, world." );
}
@end
Now it's time to actually create your first object and call an instance
method. Change main.m to look like this.
Download ObjectsAndProperties/HelloWorld/partial/main3.m
Line 1
#import <Cocoa/Cocoa.h>
-
#import "Greeter.h"
-
-
int main(int argc, char *argv[])
5
{
-
// WARNING: Do not use this code
-
Greeter * myGreeter = [[Greeter alloc] init];
-
[myGreeter sayHello];
-
10
return NSApplicationMain(argc,
(const char **) argv);
-
}
The right side of code in line 7 is the template code you will use to
create new objects. Here the variable myGreeter points to the object of
type Greeter that you are creating. We'll gloss over what's involved in
calling alloc then init and talk more about it in Section 6.7, Creating a
new Object (responsibly), on page 87. In line 8 you send the message
sayHello to myGreeter.
Please note the warning included in the code. Something is wrong. We
have behaved irresponsibly. This is such a small program and it lives
for such a short while that it isn't a critical problem but we should get
in the habit of coding correctly.
The problem is that we've created a memory leak. The Greeter object is
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
FINDING LEAKS WITH INSTRUMENTS
81
Figure 6.1: Configuring the Garbage Collector
instantiated and used and then hangs around for as long as the GUI
window remains open without ever being used again. I'll show you how
you might find your leaks and then I'll show you two ways to fix it.
6.4
Finding Leaks with Instruments
Look inside of the Developer/Applications directory where you installed
Interface Builder and Xcode and you will find a powerful profiling tool
named Instruments. I'm just going to take you quickly through how I
used Instruments to find this leak in our code.
To make sure that there is a leak for Instruments to find you will have to
make sure that garbage collection is turned off for this project (see Fig-
ure 6.1). In Xcode go to Project > Edit Project Settings and select the Build
tab. Select "All Configurations" and "All Settings" and enter "garbage" in
the search field. You can then choose from three values for Objective-
C Garbage Collection from the drop down box: "Unsupported", "Sup-
ported", or "Required". Choose "Unsupported" and close the settings
window. Set the Active Build Configuration to "Release" and Build & Go.
You've now created a release version of the application that we can test
with Instruments.
Start up Instruments. You will be presented with an assortment of tem-
plates to choose from for your trace document. Choose "Leaks". Since I
am only interested in leaks I have deleted the "ObjectAlloc" instrument
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
FINDING LEAKS WITH INSTRUMENTS
82
Figure 6.2: Finding Leaks with Instruments
that is included as part of this template by selecting it and pressing the
delete key. You can just leave it there.
Now that you have selected your instrument you need to set your "Hello
World" application to be the target. Click on the drop down list for the
"Default Target" and choose Launch Executable > Choose Executable ....
Navigate using the finder in the dialog that appears to select the release
version you just built for your application. It should be inside your
project directory at HelloWorld/build/Release/HelloWorld.app. Press open
and Instruments is now ready to go.
Press the "Record" button and "Hello World" will launch and you can
watch for the leak to be reported. Once it does press the "Stop" button.
Your results should look something like Figure 6.2.
Whenever you create an object, you're using a small chunk of mem-
ory. If, sometime later, you're no longer using that object, that mem-
ory chunk is still spoken for. Over the lifetime of a program, you may
end up creating thousands or millions of transient objects. If you don't
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
FINDING LEAKS WITH INSTRUMENTS
83
arrange for these to be tidied up, all that memory will just be wasted
(and, what's worse, unavailable to other applications). So, as responsi-
ble developers, we make sure that all these waste objects are returned
to the pool of available memory. This process is called garbage collec-
tion.
Now, here's the good news. Starting with OSX 10.5, Objective C will
automatically keep an eye on all objects, and will automatically collect
objects that are no longer being referenced.
You might think that this takes this particular responsibility away from
you. But, here's the problem. There are two significant platforms where
there is no automatic garbage collection. The first is any prior versions
of OSX. If the users of your application are running Tiger, for exam-
ple, and you don't do any memory management in your application,
they'll seem your application as a memory hog. Possibly even more sig-
nificantly, if you want to write applications that run on the iPhone or
iPod Touch, you should know that neither of these environments has
support for garbage collection. And both of these are more resource
constrained than your average desktop--if you don't do decent memory
management here, the chances are good that your applications will not
be viable.
So, for these two reasons, I'm going to recommend that you write your
applications using the conventions in the next section. Do this, and
your objects will be garbage collected when no longer in use, regardless
of the platform on which your applications run.
Before we take a look at reference counting, you can briefly enable
garbage collection and verify that our memory leak goes away. In Xcode
set the value for Objective-C Garbage Collection to be "Supported". With
Garbage Collection turned on, build the "Hello World" application and
go back to Instruments and press the "Record" button and watch for
leaks. You shouldn't see any. Garbage Collection has addressed our
memory leak.
Before we go on, set the Objective-C Garbage Collection value back to
"Unsupported" in Xcode. Let's work for the remainder of this chapter
without our safety net to get a feel for what is involved in managing
your own memory.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
REFERENCE COUNTING
84
6.5
Reference Counting
Memory management is a balance of many constraints. The most ele-
mentary is that you don't want to release the memory for an object
while something else might still be pointing to it. On the other hand,
you don't want a memory leak by hanging on to objects long after no
one needs them anymore. One way to accomplish this in Objective-C is
through reference counting.
When you create an instance of a class, the reference count for that
object is set to one. You've seen briefly that you create an object of type
Greeter with code that looks like this.
Greeter *myGreeter = [[Greeter alloc] init];
The retain count is set to one when alloc is called. You may also want
to take ownership of an object that has been created elsewhere. When
you are given an object you need to hold on to you are responsible
for sending a retain message to the object. This increases its reference
count by one. Whether you create an object using alloc or hold onto one
using retain, you now own that object and are responsible for releasing
it when you no longer need it.
When you are done with an object you are responsible for sending a
release message to the object which decreases its reference count by
one. When an object's reference count is zero its dealloc will be called
to clean up the object resources and release the memory.
This sounds pretty straightforward--and for the most part it is. But as
you'll soon see, there are patterns you are going to have to get used to.
Mainly it is up to you to release objects that you create or retain and
not to release any objects that you didn't create or retain.
Here's an example of the retain--release pattern you'll use when chang-
ing the value of an object. Suppose you already have created an object
named myGreeter of type Greeter. An idiomatic setGreeter: method using
the retain-release pattern would look something like this.
- (void) setGreeter: (Greeter *) newGreeter {
[newGreeter retain];
[myGreeter release];
myGreeter = newGreeter;
}
You are being sent the object newGreeter. You want to own it to do
something with it so it is your job to retain it. On the other hand, you
are about to set the variable myGreeter to point to the object referenced
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
THE AUTORELEASE POOL
85
by newGreeter. You don't need the object that myGreeter pointed to so
you release it. Now you set myGreeter to point to what newGreeter points
to. Even though you are never going to use the variable newGreeter
again, you are going to use what it pointed to. This whole retain-release
cycle was retaining and releasing what the variables point to and not
the variables themselves.
This retain/release method of memory management does have parallels
in every day life. For example, the conference hotel where I wanted
to stay was completely booked. They put me on the waiting list and
I booked a room at a nearby hotel. I entered all of this information
in iCal. Fortunately, a room opened up at the conference hotel. Think
for a minute about the order in which you make the changes and you
will see that it corresponds completely to how you handle memory in
Objective-C.
First I retained a reservation for the new room in the conference hotel.
Then I released my existing reservation at the nearby hotel. Finally,
I reset the information in iCal to contain the information about the
changed reservation. So if you think of the iCal event as our variable, I
retained the object the variable would point to, I released the object the
variable currently points to, then I set the variable to point to the new
object.3
6.6
The Autorelease Pool
There's another situation we haven't taken care of yet. Imagine that you
have the situation in the following pseudocode.
- (Greeter *) sendBackAGreeter {
Greeter *myGreeter = [[Greeter alloc] init];
[myGreeter doSomething];
// how do I release myGreeter and still do the following step
return myGreeter;
}
You can't just release myGreeter or the retain count will be zero and it
will be dereferenced before you try to return it. You can't do nothing or
you will have a memory leak where you may be holding on to myGreeter
after no object is really using it because you haven't decremented its
retain count.
3.
To stretch the metaphor, having a travel agent is the analog of turning on automatic
Garbage Collection in this hotel reservation example.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
THE AUTORELEASE POOL
86
Joe Asks. . .
Why didn't we need Autorelease pools before?
Up until now our entire application was being controlled from
GUI events. App Kit sets up and releases Autorelease pools for
you. If we are working outside of the AppKit code (as we are
here) or on a separate thread we need to set up and release
our own Autorelease pool.
By the way, you can also learn from AppKit and set up multi-
ple Autorelease pools. If you have an area of code where a
lot of objects are being created and autoreleased, create an
Autorelease pool to handle the disposal of those objects.
The solution is to use an autorelease pool.4 You send myGreeter the
autorelease message and it is marked to be released once this method
completes like this example that might sit in a fictional GreeterVendor
class.
- (Greeter *) sendBackAGreeter {
Greeter *myGreeter = [[Greeter alloc] init];
[myGreeter doSomething];
[myGreeter autorelease];
return myGreeter;
}
You also need to create an instance of an NSAutoReleasePool at the begin-
ning of the body of your main( ) and you need to explicitly release the
pool at the end.
Download ObjectsAndProperties/HelloWorld/partial/main4.m
#import <Cocoa/Cocoa.h>
#import "Greeter.h"
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Create and use objects in here
[pool release];
return NSApplicationMain(argc,
(const char **) argv);
}
4.
For more information on Autorelease pools and memory management see Apple's
publication "Memory Management Programming Guide for Cocoa."
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING A NEW OBJECT (RESPONSIBLY)
87
6.7
Creating a new Object (responsibly)
At this point you are explicitly creating two objects in code. One of
them, myGreeting, was an instance of the class TemporaryGreeting which
we created ourself. The other, pool was an instance of NSAutoreleasePool.
Here's the general format for creating a new object of type Foo.
Foo *myFoo = [[ Foo alloc ] init ];
If you follow this pattern you will be creating instances in three steps
merged into one. Some books will initially separate the steps into three
lines.
// Do not initialize an object this way
Foo * myFoo;
myFoo = [Foo alloc];
[myFoo init];
This has the advantage of highlighting the three stages involved.
· Declare the variable myFoo as a pointer to an object of type Foo.
· Call the class method alloc. This method creates an instance of
the class with most of its variables set to the appropriate value of
zero (0, 0.0, nil, or NULL).
Every object has an isa variable that points to the object's class.
This is how you were able to find myGreeter's class. This value is
set by alloc. The final activity performed at this step is to set the
object's reference count to one.
· The init method is an instance method that is used to initialize the
instance variables of the myFoo object and perform any needed
initial configuration.
It is best to combine at least the alloc and init steps. So you should
create and initialize an object in one line as you've seen before when we
created the myGreeter object.
Foo *myFoo = [[ Foo alloc ] init ];
Remember that alloc sets the reference count to one. So after the line
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
the reference count for pool is one. That's why we need to end with the
line
[pool release];
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING A NEW OBJECT (RESPONSIBLY)
88
This sets the reference count for pool to zero. Even when the process is
ending, it is a good to get in the habit of picking up after yourself.
OK, let's get back to our example. It's not very satisfying. We create
an object but we never really use it. We just use the object to call an
instance method but our object doesn't have any state. We might as
well have used a class method.
Let's fix all that. We'll introduce a variable named greetee. We need to
declare this variable in the header file Greeter.h.
Download ObjectsAndProperties/HelloWorld/partial/Greeter5.h
#import <Cocoa/Cocoa.h>
@interface Greeter : NSObject {
NSString *greetee;
}
- (void) sayHello;
@end
Let's set the value @"world" when we create our Greeter object. To set
the value of greetee we'll implement our own version of init. The pattern
here is to first call up the chain. You then perform all initialization that
is special to this object that isn't taken care of by the super classes
and return the object. If there was a problem initializing the object
in the superclass you return nil. If you anticipate a problem with your
initialization you should test for it and return nil if it fails. In pseudocode
this looks something like this.
- (id)init {
if (self = [super init]) {
// do some initialization
if (the initialization is fine) {
return self;
} else {
[self release];
return nil;
}
} else return nil;
}
We'll use a simpler version of this init on lines 9-15. We do check that
we get a valid object back from [super init] but we are not checking that
we could initialize the value of the greetee.
Download ObjectsAndProperties/HelloWorld/partial/Greeter5.m
Line 1
#import "Greeter.h"
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
CREATING A NEW OBJECT (RESPONSIBLY)
89
-
-
@implementation Greeter
-
5
- (void) sayHello {
-
NSLog(@"Hello, %@." , greetee);
-
}
-
-
- (id)init {
10
if (self = [super init]) {
-
greetee = @"world" ;
-
return self;
-
}
-
else return nil;
15
}
-
@end
We'll also have to change the body of sayHello to insert the value of
greetee at the appropriate place in the greeting. In line 6 you can see
that we do this the same way you have inserted floats and ints and
other primitives into formatted strings in other languages. Here we use
%@ as a place holder for an NSString and pass in the value of the string
as an argument.
Just before the memory is released the dealloc is called. Add this basic
dealloc method to the end of your Greeter.m file.
- (void) dealloc {
[greetee release];
NSLog(@"Releasing a Greeter object." );
[super dealloc];
}
Just as [super init] is the first thing you call in init, you should make sure
that [super dealloc] is the last thing called in dealloc. Before that point
you release any of the objects you still own.
You need to to make one change to your main.m file. The autorelease
pool takes care of objects that are autoreleased but it doesn't release
myGreeter for us. We need to explicitly release myGreeter because we
own it. The call to release the object is shown in the highlighted line
below.
Download ObjectsAndProperties/HelloWorld/partial/main5.m
#import <Cocoa/Cocoa.h>
#import "Greeter.h"
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
REFACTORING OUR CODE
90
Figure 6.3: Renaming a Variable
Greeter * myGreeter = [[Greeter alloc] init];
[myGreeter sayHello];
[myGreeter release];
[pool release];
return NSApplicationMain(argc,
(const char **) argv);
}
Clear your Console, Build & Go and you'll find that you aren't leak-
ing memory any more. You should see a message in the Console when
sayHello is called and another when myGreeter is released.
6.8
Refactoring our Code
Now and then it helps to step back from your code and look to see what
could use some cleaning up. Now that I look at it, I don't like the name
myGreeter. Really, it is used just for greeting the world. I'd like to change
the name to worldGreeter.
I don't know if you've noticed it yet but when you click on myGreeter
(go ahead--click on it) all instances of myGreeter are underlined. If you
hover your mouse around myGreeter slightly to the right you will get a
drop down arrow. Click on it to get your options.5 Click on the arrow
and you should see the options shown in Figure 6.3.
Choose Edit All in Scope. Both instances of myGreeter are highlighted.
Change the "my" to "world" in one of them and you'll see that they both
are changed to worldGreeter.
5.
This is the same interface you see when you hover over a phone number or an address
that is part of an email you read in Mail.app.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
REFACTORING OUR CODE
91
Figure 6.4: Extract Refactoring
Next, although it doesn't look to be a problem right now, the main( )
could easily get out of hand. Let's perform a quick refactoring and pull
out these two lines into a separate method.
Greeter * worldGreeter = [[Greeter alloc] init];
[worldGreeter sayHello];
We could easily do this refactoring by hand, but let's use Xcode's refac-
toring tools. Select those two lines and choose Edit > Refactor ... or B D J.
The refactoring dialog box will appear and after analyzing the code you
selected will present you with a drop down list of available refactorings.
Choose "Extract". The default signature of the new function is void
extracted_function(). Change it to void greetTheWorld(). Press the "Preview"
button. You will get a list of all of the files where there are changes. In
this case the only changes that will result from this refactoring are in
main.m. There are two of them. Click on main.m in the refactoring win-
dow and you should see the preview shown in Figure 6.4.
Press the "Apply" button to complete the refactoring. Here is the main.m
file that results.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: OTHER INITIALIZATIONS
92
Download ObjectsAndProperties/HelloWorld/partial/main6.m
#import <Cocoa/Cocoa.h>
#import "Greeter.h"
void greetTheWorld(void) {
Greeter * worldGreeter = [[Greeter alloc] init];
[worldGreeter sayHello];
[worldGreeter release];
}
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
greetTheWorld();
[pool release];
return NSApplicationMain(argc,
(const char **) argv);
}
6.9
Exercise: Other Initializations
As always happens with this example, after a while you'd like to greet
something other than the whole world. Fortunately, we're in great shape
to do so. If we could only reset the greetee we could greet anyone we
want.
One way to add a bit of flexibility would be to create a second init
method. This one would take the name of the greetee as a parame-
ter. In fact, as long as we begin the name of the method with "init" we
are free to give it a more descriptive name. We could call it initWithName:.
Take a minute to do just that. Add an initWithName: method that takes
an NSString * as its only parameter and returns an id. The initWithName:
method will have to set the value of greetee to the string that is passed
in.
In main.m create a method named greetAPerson( ) that takes an NSString *
as its only argument and creates a personGreeter using Greeter's initWith-
Name: method.
6.10
Solution:Other Initializations
You don't have to make any changes to the header file. For Greeter.m
add this method.
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
PROPER TIES
93
Download ObjectsAndProperties/HelloWorld/partial/Greeter7.m
- (id)initWithName:(NSString *) name
{
if (self = [super init]) {
greetee = name;
return self;
}
else return nil;
}
You can now refactor the init to call initWithName: to eliminate the dupli-
cated code.
Download ObjectsAndProperties/HelloWorld/partial/Greeter7.m
- (id)init {
return [self initWithName:@"world" ];
}
Add the following method to main.m
Download ObjectsAndProperties/HelloWorld/partial/main7.m
void greetAPerson(NSString * personName) {
Greeter * personGreeter = [[Greeter alloc] initWithName:personName];
[personGreeter sayHello];
[personGreeter release];
}
Finally, you need to call this new method from main( ).
Download ObjectsAndProperties/HelloWorld/partial/main7.m
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
greetTheWorld();
greetAPerson(@"Daniel" );
[pool release];
return NSApplicationMain(argc,
(const char **) argv);
}
The result of these changes should be code that produces
Hello, world.
Hello, Daniel.
6.11
Properties
Adding a second custom init gave you some flexibility. You were able to
create a new Greeter instance with a different name for your greetee.
This allows you flexibility at the time of instantiation--sometimes this
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
PROPER TIES
94
is exactly what you want. Other times you want to be able to use getters
and setters to retrieve and assign values to your object's properties.
Use the getter named foo to retrieve the value of a property called foo
which is of type Foo. The getter would look like this.
- (Foo *) foo {
return foo;
}
If foo is a member of the object myGreeter then you could get this value
either by calling
[myGreeter foo];
Or, if you are using at least Objective-C 2.0, you can use the syntactic
sugar
myGreeter.foo;
Similarly, if you want to set the value of foo you would create a setter
called setFoo: that would look like this.
- (void) setFoo: (Foo *) newFoo {
[newFoo retain];
[foo release];
foo = newFoo;
}
As with the getter you can call the setter in two ways. If someFoo is an
instance of type Foo the traditional way is
[myGreeter setFoo: someFoo];
and again with Objective-C 2.0 you have the option of using this
myGreeter.foo = someFoo;
In order to take advantage of the dot syntax you have to declare your
getter and setter in your header file. In our case, that means that our
header file now looks like this.
Download ObjectsAndProperties/HelloWorld/partial/Greeter8.h
#import <Cocoa/Cocoa.h>
@interface Greeter : NSObject {
NSString *greetee;
}
- (void) sayHello;
- (void) setGreetee:(NSString *) newGreetee;
- (NSString *) greetee;
@end
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
PROPER TIES
95
We implement the getter and setter in Greeter.m like this.
Download ObjectsAndProperties/HelloWorld/partial/Greeter8.m
- (NSString *) greetee{
return greetee;
}
- (void) setGreetee: (NSString *) newGreetee{
[newGreetee retain];
[greetee release];
greetee = newGreetee;
}
Let's talk a little more in depth about how we take it for a spin. Here is
the method we've added to main.m.
Download ObjectsAndProperties/HelloWorld/partial/main8.m
Line 1
void greetAnotherPerson(NSString * personName){
-
Greeter * changingGreeter = [[Greeter alloc] init];
-
NSLog(@"Right now the greetee's name is %@" , changingGreeter.greetee);
-
[changingGreeter sayHello];
5
changingGreeter.greetee = personName;
-
NSLog(@"Right now the greetee's name is %@" , [changingGreeter greetee]);
-
[changingGreeter sayHello];
-
[changingGreeter setGreetee:@"world (again)" ];
-
[changingGreeter sayHello];
10
[changingGreeter release];
-
}
You can see that I used the two different versions of getters on lines 3
and 6. You can see the two different versions of the setters on lines 5
and 8. We call greetAnotherPerson( ) as part of main( ).
Download ObjectsAndProperties/HelloWorld/partial/main8.m
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
greetAnotherPerson(@"Daniel" );
[pool release];
return NSApplicationMain(argc,
(const char **) argv);
}
Build & Go and, stripping the time code and identifiers as usual, the
result is:
Right now the greetee's name is world
Hello, world.
Right now the greetee's name is Daniel
Hello, Daniel.
Hello, world (again).
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
EXERCISE: HELLO GUI
96
Most of the time, the getters and setters are just boilerplate code. If all
you need for accessors are the basic patterns we tend to follow then
Apple will synthesize them for you. You just mark the instance vari-
ables as properties in your header file and indicate which accessors
you want to generate. So for our greetee you would replace the explicit
declarations of the getter and setter with the highlighted line.
Download ObjectsAndProperties/HelloWorld/partial/Greeter9.h
#import <Cocoa/Cocoa.h>
@interface Greeter : NSObject {
NSString *greetee;
}
- (void) sayHello;
@property (readwrite, retain) NSString *greetee;
@end
Your choices are to declare just a getter (using readonly) or a getter and
a setter (using readwrite). You can also indicate whether the setter uses
simple assignment (using assign), the object is retained on assignment
(using retain), or whether a copy is used for assignment (using copy). The
remaining options include specifying a non-default name for the getter
and setter or changing accessors to not being atomic (using nonatomic).6
So far all you've done is replace the declarations in the header and
specify what greetee and setGreetee: should look like. You can Build &
Go and you will get the same output as before.
Step two of this process is to generate the accessor methods based on
what you've specified with this property declaration in your header file.
Replace the greetee and setGreetee: in your implementation file with
this single line.
@synthesize greetee;
Build & Go and the accessors are created for you and everything works
as before.
6.12
Exercise: Hello GUI
You've learned a lot in this chapter. As a final step, let's create a View
and a Controller to go with ourGreeter class Model.
6.
Read more about Properties in Apple's documentation on "The Objective-C 2.0 Pro-
gramming Language".
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SOLUTION: HELLO GUI
97
From Xcode create a new Objective-C class named GreeterController. It
will declare a single instance variable called changeableGreeter that
points to a Greeter object. The header should declare a single action
named issueGreeting. Don't forget that the header needs to import the
header file for Greeter now.
In Interface Builder drag an editable text field onto your window. Also
create a GreeterController object inside of the document window. Wire
the two of them together so that when the user presses the enter key
the issueGreeting method will be called.
Next, you need to implement the GreeterController. When it awakes from
Nib it should instantiate the Greeter and save it as changeableGreeter.
Implement issueGreeting to take the string value from the text field and
make that the value of changeableGreeter's greetee. Then issueGreeting
should ask changeableGreeter to sayHello.
As a last step, turn on automatic garbage collection and clean up
main.m. Everything is driven by the UI so main.m can go back to the
file you originally created.
Download ObjectsAndProperties/HelloWorld/main.m
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc,
(const char **) argv);
}
When you have finished, you should have created our first MVC appli-
cation. The View and the Model know nothing about each other and
there is very little logic in the Controller other than the code that hooks
the two together.
6.13
Solution: Hello GUI
You just saw that the code for main.m could be reduced for this exercise.
You can also shrink the code for Greeter.m because there is no reason
for either of the two init methods.
Download ObjectsAndProperties/HelloWorld/Greeter.m
#import "Greeter.h"
@implementation Greeter
- (void) sayHello {
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SOLUTION: HELLO GUI
98
NSLog(@"Hello, %@." , greetee);
}
@synthesize greetee;
@end
We could reduce the footprint further by eliminating the instance vari-
able and changing the method called to sayHelloTo: and pass in the name
of the greetee. This way the header file for Greeter stays the same.
Download ObjectsAndProperties/HelloWorld/Greeter.h
#import <Cocoa/Cocoa.h>
@interface Greeter : NSObject {
NSString *greetee;
}
- (void) sayHello;
@property (readwrite, assign) NSString *greetee;
@end
Next, create your new class GreeterController. The controller needs to
bridge between the View and the Model. In this case, the Controller is
creating an instance of the Model and it is providing an IBAction that the
View can call. That's it. So GreeterController.h looks like this.
Download ObjectsAndProperties/HelloWorld/GreeterController.h
#import <Cocoa/Cocoa.h>
#import "Greeter.h"
@interface GreeterController : NSObject {
Greeter *changeableGreeter;
}
-(IBAction) issueGreeting:(id) sender;
@end
While we're here, let's finish writing the code. In the implementation of
GreeterController we need to create an instance of a Greeter and then use
it when issuing greetings to set the name of the greetee and to say hello.
Notice that we don't need an IBOutlet for the text field since we can use
the sender to get its string value.
Download ObjectsAndProperties/HelloWorld/GreeterController.m
#import "GreeterController.h"
@implementation GreeterController
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
SOLUTION: HELLO GUI
99
- (void) awakeFromNib {
changeableGreeter = [[Greeter alloc] init];
}
- (IBAction) issueGreeting: (id) sender {
[changeableGreeter setGreetee:[sender stringValue]];
[changeableGreeter sayHello];
}
@end
As for the GUI, drag a text field from the Library into the window and
resize it the way you want. In the Attributes Inspector use the drop
down to set the "Action" to be "Sent on enter only" and make sure the
"Editable" checkbox is checked.
Next drag an Object from the Library into the Document window. In the
Identity Inspector set its class to be the GreeterController. In the Con-
nections Inspector connect the "Received Action" named issueGreeting:
to the text field.
Save your work. Build & Go. Type what ever string you want in the text
field and hit "enter" and that string will be echoed in the Console as
part of "Hello, whatever you entered."
Report erratum
Prepared exclusively for Jesper Noehr
this copy is (B1.0 printing, September 17, 2008)
Index
Prepared exclusively for Jesper Noehr
Document Outline
- Contents
- Introduction
- Using What's There
- Obj-C Insights: Methods and Parameters
- Creating a Controller
- Creating your controller class
- Creating an instance of our controller in IB
- Outlets and Actions
- Adding Outlets and Actions to the Header
- Wiring up the Controller
- Implementing the Loading of the Previous Page
- Exercise: Finish the controller
- Solution: Finish the controller
- Disabling and Enabling the Buttons
- Still needs work
- Listening to Delegates and Notifications
- Delegates
- Setting the Window Title
- Exercise: Updating the URL and Setting Buttons
- Solution:Updating the URL and Setting Buttons
- Cleaning up
- Notifications
- Awake from Nib
- Adding a Progress Indicator
- Spelling Counts
- Implementing the Load Indicator
- Exercise:Enhancing the Progress Indicator
- Solution:Enhancing the Progress Indicator
- Obj-C Insights: Objects and Properties
- Hello, World
- Creating a new Class
- Creating a new Object (irresponsibly)
- Finding Leaks with Instruments
- Reference Counting
- The Autorelease Pool
- Creating a new Object (responsibly)
- Refactoring our Code
- Exercise: Other Initializations
- Solution:Other Initializations
- Properties
- Exercise: Hello GUI
- Solution: Hello GUI
- Index
hr>