It’s time to start thinking about the Objective-C object that will control our Calculator — that is, respond to button clicks, calculate the values that the user wants, and display the results. By convention, this kind of object, which performs behind-the-scenes work and communicates with the user interface, is called a Controller .
Controllers generally don’t have main event loops; instead, they perform actions in response to events that are received and interpreted by other objects. A good rule of thumb is to place as little code in your Controller as is necessary for it to do its job. If it is possible to create a second Controller that is used only for a clear, particular purpose, do so — the less complicated you make your application’s objects, the easier they are to debug. In addition to controlling the overall flow of the application, Calculator’s Controller will contain the code to perform the basic arithmetic and thus can be thought of as the computational engine or back end (albeit a very simple one) of the application.
Cocoa doesn’t provide you with a Controller class — it’s up to you to write one for your application. (IB and the AppKit are fabulous, but they can’t do everything for you — at least not yet!)
Before you start coding, it’s a good idea to sit down and think about your problem. What does the Controller have to do? What kind of messages will it need to respond to? What kind of internal state does it have to keep in order to perform those functions? Recall that we want our Calculator to allow a user to type in the sequence “2*5=” by clicking four buttons in a window and to display (in order) 2, 2, 5, and 10 in a text output area. Thus, for our Calculator, the answers are fairly straightforward.
Here’s what our Calculator must do:
Clear the display and all internal registers (value holders) when a “clear” button is clicked.
Allow the user to click a digit button on the numeric keypad and display the corresponding digit immediately after it is typed.
Allow the user to click a function button (e.g., “add”, “subtract”).
Clear the display when the user starts entering a second number.
Perform the appropriate arithmetic operation when the user presses the “equals” button or another function button.
Our Calculator must also maintain the following state to perform these functions:
The first number entered
The function button clicked
The second number entered
It turns out that to work properly, our Controller object needs two more pieces of information:
A flag that indicates when a function button has been clicked — if the flag is set, the text display area (which we’ll call readout) should be cleared the next time that a digit button is clicked, because the user is entering a second number
The location in the readout text display area where the numbers should be displayed
These bullets indicate that we are using Objective-C to create a simulation of a real, physical calculator. That’s what object-oriented programming is often about: constructing progressively better simulations of physical objects inside the computer’s memory, and then running them to get real work done. When the simulation is functionally indistinguishable from the real-life object being simulated, the job is finished.
Every Objective-C class, except NSObject, is based on (and inherits from) another class. The NSObject class itself is the most fundamental Objective-C class, because it defines the basic behavior of all objects and is at the root of all inheritance hierarchies. Because we don’t need any special behavior in our Calculator other than what is already defined in the AppKit, our Controller class will be a subclass of NSObject.
We’ll start building our Controller class by subclassing it from the NSObject class in IB.
Click the Classes tab in IB’s Nib File window to view the AppKit’s object hierarchy.
Scroll to the far left in the Classes pane using the horizontal scroller at the bottom of the Nib File window, and then select the NSObject class by clicking it. (See Figure 5-10.) You can also rapidly jump to the NSObject class by typing the word “NSObject” into the Classes pane’s Search field.
The NSObject class name is displayed in gray, which means that you can’t change any of its properties or built-in behaviors without subclassing it. So that’s what we’ll have to do.
Click IB’s Classes menu item at the top of the screen, then choose Subclass NSObject, as shown in Figure 5-11 (or simply hit the Return key when NSObject is highlighted).
A new class called MyObject will appear under NSObject in the class hierarchy.
Change the name from “MyObject” to “Controller”, and hit Return. (See Figure 5-10.)
You’ve just created a new Objective-C class called Controller. Right now it doesn’t do anything different from the NSObject class. Next, we’ll give the Controller class some custom behavior by adding some outlets and actions.
Cocoa uses a powerful system known as “outlets and connections” to give you an easy way to send messages between user interface objects such as windows, buttons, other controls, and your own custom objects. An outlet is simply an instance variable in an Objective-C class that has the type id and thus can store a pointer to an object. The value of this instance variable is usually set to the id of another object in the nib — that is, a user interface object. Thus, outlets normally point to interface objects.
When an outlet is set to store the id of another object in the nib file, IB calls this a connection. Cocoa maintains connections for you. When object specifications are saved in a nib file, the connections you set up between them in IB are saved as well. These connections are automatically restored when the nib file is loaded back into IB.
Outlets can also be given a specific type. When you do so, IB will give you a warning if you attempt to connect the outlet to an object that is not of that type (or a class of that type).
For example, suppose that you have two object specifications in a nib file: objects A and B. Suppose also that object A contains an outlet, or id variable, that points to object B. When Cocoa loads this nib file, it will first create new instances of object A and object B, then will automatically set the outlet in object A to point to object B; that is, it sets the outlet A to be the id of object B.
Outlets therefore give you an easy way to track down the ids of objects that are dynamically loaded with nib files. They are the mechanism that Cocoa provides for wiring up an interface in IB without writing any code.
There are two ways to add
outlets to a class: either by entering
them in IB’s Class Info dialog, or by hand, using an
editor to type them into the class interface
(.h
) file for your class. In the latter case,
you can choose IB’s Classes → Read
Files menu command to inform IB about the outlets that exist for the
class. We’ll see how to add outlets using IB in this
chapter and by hand in the next chapter.
After adding an outlet, you use IB to initialize where it points. You do this by setting up a connection from the object containing the outlet to the object to which you want it to point, and then choosing the outlet from the list of outlets in IB’s Connections Info dialog. When you make a connection between an outlet in an object and another object in IB, IB sets the instance variable in the first object to the id of the object to which it is connected. That’s all!
In the following steps, we’ll add and initialize an
outlet called readout
in IB.
If necessary, select Controller in the Classes pane of IB’s Nib File window.
Now type Command-1 to display IB’s Attributes Info dialog for the Controller class. The Info dialog window should be titled “Controller Class Info”.
Make sure that the Objective C radio button is selected in the Attributes Info dialog, as shown in Figure 5-12.
Click the Add button at the bottom of the Controller Class Info
dialog, and the outlet called myOutlet
will
appear.
Change the name of this outlet to “readout” by typing the new name followed by Return.
If you make a mistake, you can double-click the outlet to change the name again. You can also click the Remove button to remove an outlet. When you’re done, the Class Info dialog should look like the one on the right in Figure 5-12.
We’ll eventually set the readout
Controller outlet to point to the Calculator’s text
display area (NSTextField) object in the Calculator window. Then the
Controller will be able to send messages to the NSTextField via the
outlet.
Next, we’ll add action methods to the Controller class.
An action is a special type of Objective-C method. Action methods are special because they take a single argument called sender , the id of the object that sent the message invoking the action method. Using IB, we can arrange for an object’s action method to be invoked automatically in response to a user event, such as a button click, menu choice, or slider drag. Thus, an action method is an event handler .
In Chapter 3, we used the takeIntValueFrom: action method to make an NSTextField automatically take its value from the NSSlider object when the slider knob was moved (see Figure 3-16). Here, we’ll create our own action methods in the Controller class and arrange to have them invoked when the user clicks our Calculator’s buttons.
Click the 0 Actions tab in the Controller Class Info dialog.
Click the Add button at the bottom of the Controller Class Info dialog; the myAction: action will appear, as shown in Figure 5-13.
Figure 5-13. Creating an action (left) in the Controller Class Info dialog; four actions in Calculator (right)
Rename myAction: as clear: and hit Return.
Add the clearall:, enterdigit:, and enterOp: actions to your Controller class in a similar fashion. You don’t have to type the colons (:) when renaming actions, because IB will automatically append them.
Notice that IB alphabetizes the actions as you add them. Your Controller Class Info dialog should now look like the one on the right in Figure 5-13.
In light of our discussion of the design of the Controller class, the function of these four actions should seem fairly self-evident. We’ll go over the details later.
Notice that there’s only one action to handle all of the digit button clicks (enterDigit: ) and only one action to handle all of the function buttons (enterOp: ). We’ll determine which digit or function button is clicked by using the single argument of these actions, the id of the message’s sender. By querying the sender of the action message, the enterDigit: and enterOp: methods can determine which digit or function button was clicked. The method will then perform the appropriate action. This is a much more economical means of method dispatch than creating a separate method for each button on our Calculator — it takes less code and it runs virtually as fast.
Now that
we’ve set up an outlet and several actions for our
Controller class, we need to tell IB to create the
Controller.h
class interface file and the
Controller.m
class implementation file. Then
we’ll add the appropriate functionality (code) to
these class files and eventually compile them with the Objective-C
compiler. IB’s Create Files for Controller command
in the Classes menu generates these files from the class
specifications we made in the Nib File window and the Class Info
dialog.
Make sure the Controller class is selected in the Classes pane of the Nib File window.
Choose Classes → Create Files for Controller.
A sheet will unfold from the Nib File window enabling you to specify
the filenames for the class files to be created, as shown in Figure 5-14. Because the name of the class is Controller,
the default names for the class files are
Controller.h
and
Controller.m
. After creating these class files,
IB will insert them into the Calculator project.
Click Choose on the sheet to use the default filenames
(Controller.h
,
Controller.m
) and insert them into the
Calculator project.
Now click the PB icon in your Dock to see how these class files fit into your project.
The two new files should be located in the Classes group but may be located in the Other Sources group. If you like, you can move these files from one file group to another in PB’s Groups & Files pane; the organization is for your benefit only and is ignored by PB. When you have a large project, you may even want to create your own groups and subgroups of files.
These new Controller class files contain only a skeleton of what we want in the Controller class. To make our Controller work, we have to add some logic and write some Objective-C code.
Click the Finder icon in your Dock and investigate which files have been created as part of your Calculator project — there are several!
These project files reside in your ~/Calculator
folder. We recommend that you compare the files in the
~/Calculator
folder in the Finder with those
listed in the Groups & Files pane in PB.
To make the Controller work, we need to understand a little bit about a four-function calculator. The basic four-function calculator has three registers: an X and a Y register, both of which hold numbers, and an operations register, which holds the current operation. The readout always displays the contents of the X register. Clicking a function button stores that function in the operations register and sets a flag. If the flag is set, the next time a digit button is clicked, the number in the X register is moved to the Y register and the X register is set to 0.
We’ll get the Controller class working in stages, testing them one at a time. Generally, this is a good approach to writing any program, large or small. Object-oriented programming makes it easy to test the individual parts, because they are all fairly self-contained.
First, we’ll get numeric entry and the clear keys working. Later, we’ll handle the arithmetic functions.
Back in PB, click Controller.h
in
PB’s Groups & Files pane to open the file in
PB’s main window, as shown in Figure 5-15. (If you double-click instead of single-click
Controller.h
, a separate editor-type window will
open.)
Looking at the code in PB’s window (in Figure 5-15), note that the Controller class is a subclass
of NSObject, as we specified in IB. Note also that Objective-C
declarations have been generated for the outlet
(readout
) and the four action methods in the
Controller class that you set up in IB.
Following is the Controller.h
file. The lines
generated by IB are shown in regular type, and the lines that you
need to insert are shown in bold type.
/* Controller.h */ #import <Cocoa/Cocoa.h> @interface Controller : NSObject { IBOutlet id readout; BOOL enterFlag; BOOL yFlag; int operation; double X; double Y; } - (IBAction)clear:(id)sender; - (IBAction)clearAll:(id)sender; - (IBAction)enterDigit:(id)sender; - (IBAction)enterOp:(id)sender;- (void)displayX; @end
IB generated the first two (non-bold) lines because we subclassed
NSObject to create theController
class (importing <Cocoa/Cocoa.h>
includes
the NSObject class interface file, as well as the rest of the Cocoa
classes). Because we added readout
as an outlet in
the Class Info dialog, IB also generated the IBOutlet id declaration
for it (see the sidebar IBOutlet and IBAction). Finally, IB generated the
four action method declarations because we added the four actions in
IB’s Class Info dialog. Note that the single
argument for all of these action methods, sender
,
was generated automatically.
Insert the five new instance variables and one new method indicated
by the lines shown earlier in bold type in the
Controller.h
file. We’ll
discuss the new displayX
“non-action” method a bit later.
Save the Controller.h
class file (Command-S).
Still in PB, double-click Controller.m
in the
Groups & Files pane to open a new editor-type window with the
Controller implementation code inside. (See Figure 5-16.)
Following is the Controller.m
file. As with the
Controller.h
file, we list the lines generated
by IB in regular type and the lines you need to insert in
bold type.
/* Controller.m */ #import "Controller.h" @implementation Controller - (IBAction)clear:(id)sender { X = 0.0; [self displayX]; } - (IBAction)clearAll:(id)sender { X = 0.0; Y = 0.0; yFlag = NO; enterFlag = NO; [self displayX]; } - (IBAction)enterDigit:(id)sender { if (enterFlag) { Y = X; X = 0.0; enterFlag = NO; } X = (X*10.0) + [ [sender selectedCell] tag]; [self displayX]; } - (IBAction)enterOp:(id)sender { } - (void)displayX { id s = [NSString stringWithFormat:@"%15.10g", X ]; [readout setStringValue: s]; } @end
IB generated the line that imports Controller.h
because every class implementation file must import its own interface
file. Most of the other lines generated by IB are simply stubs for
the action methods that we set up in the IB’s Class
Info dialog. IB generates code in class files more for convenience
than for any other reason.
Insert the code shown above in bold type into the
Controller.m
file.
Save the Controller.m
class file (Command-S).
The Controller class sends messages to instances of the
NSTextFieldCell and NSMatrix classes. In particular, the newly added
displayX method displays the
contents of the X register by sending the setStringValue: message to
readout
, the outlet that we created in IB. Later
we’ll use IB to initialize
readout
to point to the NSTextFieldCell object
(near the top of the Calculator window).
When a message is sent to an object of a class, the class interface
file definition for that class should be
#import
-ed in the class definition. But
#import
statements for the NSTextFieldCell and
NSMatrix class interface file definitions are not listed in the code
we’ve shown. What’s going on?
Fortunately, the #import
Controller.h
line in
Controller.m
, together with the
#import
<Cocoa/Cocoa.h>
line in Controller.h
, takes care of importing
the NSTextFieldCell and NSMatrix class interface file definitions for
us. In fact, they import all the Application Kit class definitions.
In Cocoa, the AppKit class headers are all precompiled, so
it’s quite fast to import them all, provided that
<cocoa/cocoa.h>
is imported before any
symbols are #define
-d. (A
precompiled header file has been preprocessed
and parsed, thereby improving compile time and reducing symbol table
size.) This is why IB inserts the #import
<Cocoa/Cocoa.h>
line in all class interface
files it generates.
The clearAll: method in the
Controller.m
file sets the X and Y registers to
0.0 and the two flags to false, and then sends the displayX message to
self
(the Controller object itself ) to display 0.0 in the text display
area. The clear: method is similar
but only needs to set the X register to 0.0 and then redisplay.
We’ll discuss the enterDigit: and enterOp: methods after we finish setting up
the user interface and making all the connections.
18.118.226.66