Programming Mac OS X with Cocoa for Beginners/First Cocoa program - Hello World
Previous Page: Getting around in Xcode and Interface Builder | Next Page: Objective C, the language and its advantages
In the last chapter, we made a version of "Hello World" just by adding the text to a window in Interface Builder. You may have felt that this was cheating a little, as we haven't even written a line of code yet. We'll now revisit "Hello World", but this time do it in code.
First, we need to know a little bit about writing code for Cocoa. Cocoa is programmed in Objective-C, a small extension to the C language. If you know C, Objective-C is very easy to learn. If you know C++, it's far simpler while embodying many of the same basic concepts, and more. If you know another language, you should be able to pick it up easily enough.
Objective-C, is, as its name might suggest, a version of C that supports Objects, or Object-Oriented Programming (OOP). Some people that have come from a traditional procedural language background find object-oriented a bit alien at first, but those with no programming experience at all often do better, because OOP is much closer to how we deal with things in the real world.
Most objects in the real world have a defined purpose, a set of properties, and a particular way that they are best used. Think of the everyday task of making a cup of tea. Several objects are involved, each with their particular role in the overall task. A tap provides water; a kettle raises its temperature to boiling point; a cup provides a container for the tea, and so on. The kettle for example, has particular properties — how much water it can contain, its operating voltage, the material it is made from. It also has one main job — to boil the water. In OOP terms, the boiling operation is a method of the kettle object, as is the pour into cup operation. All objects are described in terms of their properties and their behaviours (methods). It is the same in an OOP programming language.
A collection of methods and properties is defined in the idea of an object which we call a class. The class 'kettle' is just the idea of a kettle, it is not any particular kettle. When we want an actual kettle, we are then talking about a concrete example of a kettle, which in OOP programming we call an instance of the class 'kettle'. This distinction is important — we will occasionally define a class of object, but this on its own will do nothing until we make an actual concrete example of that class, or instantiate it. A class definition describes the properties and methods of the object, but only the instance actually has those properties, and is able to carry out the methods. Usually, when we are casually talking about "objects" within the program, we are talking about instances of objects.
In Objective-C, an instance of an object is referenced by a pointer variable:
NSString* myString;
This references an NSString object with the variable name 'myString'. Note: all Cocoa's built-in classes start with 'NS'. Your own classes should probably adopt a naming convention of your own device — perhaps your initials. This is just to make sure that there is no ambiguity between the classes Cocoa provides and any you create. Another example:
GCKettle* teasMaidK2;
One of the key goals of OOP programming is to hide implementation details from the user of an object — for example, we don't care whether a kettle boils the water using a resistive element or an exotic molecular jiggler device, we only care that the water boils when we press the 'boil' switch, or some short time after. Similarly, users of objects in a program should generally be designed to not care about how objects work, only that they give the expected results on demand. This means that in order to examine the properties of an object, we should not rudely peek directly at the innards of the object, but instead ask the object politely to tell us the value of a particular property. Some properties of an object are fixed, others changeable. In OOP-speak, we call this 'immutable' and 'mutable' respectively. Since we can ask for properties, or change them, or set a method going such as 'boil', all of these things are achieved in the same way, which is by sending a message to the object. Because we are hiding the implementation details of the object, all properties are in fact accessed via methods, so the distinction between them is fairly blurred.
In Objective-C, we send a message to an object by using a square bracket notation:
[teasMaidK2 boil];
This sends the message 'boil' to the kettle object 'teasMaidK2'.
We can also obtain information about properties in the same way:
NSColor* kettleColor = [teasMaidK2 color];
This sends the message 'color' to the kettle object. It responds by returning the value of the 'color' property and assigns it to 'kettleColor'.
Very often, we need to supply additional information to a method so it knows what we want. This extra information is called the method's parameters. Objective-C has a very interesting way of passing parameters, one that might look strange at first, but which has some great advantages. Suppose we have a kettle that can operate on either 220 V or 110 V AC, and we can change it using a method called setVoltage. We need to tell it what voltage to use, and it's as simple as:
[teasMaidK2 setVoltage:110];
We simply put a colon and the value of the parameter.
Where it gets interesting is when there is more than one parameter to a method. C and C++ programmers (and most others) are used to using commas to separate a list of parameters, such as:
object->setColor( 255, 128, 0 );
Looking at this, you have no idea what the three parameters actually are — you would have to look it up in the header to find the original definition, hoping that the programmer actually included the information (some languages such as C don't require parameter names here, so you could still be in the dark):
void setColor(int green, int red, int blue);
Note that if you assumed it was in order red, green, blue you'd have been wrong!
Objective-C takes a different approach, one that saves you the trouble of looking up the header to simply read the code, and it makes it much more self-documenting. For example:
[teasMaidK2 setColorWithRed:128 green:255 blue:0];
In Objective-C, the parameter names are part of the overall method name; they can't be missed out. The actual method is called "setColorWithRed:green:blue:" The colons are also part of the unique method name, so setVoltage and setVoltage: are two distinct methods. This often catches out beginners, so make a note of it.
Method calls in Objective-C can be nested as required, e.g.
[teasMaidK2 setVoltage:[mainsSupply outputVoltage]];
Note that the square brackets as used for array indices in C are still available without ambiguity:
[teasMaidK2 setVoltage:supply[2]];
Don't call us, we'll call you
editWhen should you call a method? Whenever you want information or action from another object. However, very often, it's not you that wants to know — it's Cocoa itself, and you are expected to provide the answer. In practice, writing applications with Cocoa needs both, but there are many places where you will simply be expected to provide answers on demand. Again, classically-oriented programmers often have trouble with this idea; they are used to having complete control, and asking the operating system to do things for them. OOP tends to turn things on their heads a bit — make the right object, put it in the right place and wait for it to be called upon. Again, this is more like real life, where you as an employee have a particular role — your boss will give you tasks, you carry them out. It's not your job to run the whole company, just to do your little bit. You might delegate part of your task to others, or request information from others to get the job done, but your boss doesn't care as long as he gets his results.
In this exercise, we will in fact be doing just this — creating an object that will be called upon by Cocoa to perform a little task for it. What is the task? It's to draw the famous words "Hello World" to the screen when needed.
To do this, we need to make an object that we can be sure will be called upon whenever a window's contents needs to be drawn. Later in this text, we will go into this in much more detail, but for now we'll just gloss over the basics. In Cocoa, stuff that is drawn in windows is actually drawn by objects called views. A button that you can click is a view, for example, as is the "Hello World" text that we added in the first part. Views can contain other views, so a complex hierarchy of them can exist. Here though, we will keep it simple.
We'll need a custom view that 'knows' how to write "Hello World"; that is, it contains code that will accomplish this when called upon by Cocoa.
In all OOP frameworks such as Cocoa, another main goal is to make coding as efficient as possible by maximizing reuse of code. The idea is to solve a typical kind of problem once, then allow the programmer to leverage that solution in their own work rather than starting again from scratch, which is usually just a waste of valuable time. In Cocoa, the 'problem' of drawing all sorts of things in windows has already been solved by the views classes, so we do not need to solve this problem again. Instead, we can make use of the existing code and simply extend it a little bit to do the unique thing that we want it to do, without worrying about the rest. This customisation of existing classes is called subclassing. We will subclass Cocoa's NSView class, and then write some code to make it do the unique extra thing we want.
Interface Builder contains some neat helper tools to make this task very easy. Go into Interface Builder by double-clicking "MainMenu.nib" in the left hand panel of Xcode. Double-click the Window icon to make the window come to the front. Select and delete the "Hello World" text. In the library go to the map Cocoa and submap `views & Cells` (for Xcode versions prior to Xcode 3.1 it is in the tools palette). Drag the 'CustomView' into the window and drop it.
Drag the selection handles to position the view where you'd like — for this exercise, don't make it fit exactly to the whole window; leave a bit of a margin. Note how Interface Builder gives you guidelines as you move and size the custom view. These guidelines are a boon for neatly laying out more complex interfaces.
At the moment, "CustomView" is just a placeholder for the view we are going to make, so next we need to define a class for the custom view. IB will help us. Go back to the main IB window, titled "MainMenu.nib (English)", and click the tab button marked 'Classes'. This shows a column view of lots of classes, starting with 'NS'. Find NSObject→NSResponder→NSView and select it. A number of existing NSView subclasses will show to its right, but we don't need those now — we want to add to this list. With NSView selected, choose Classes→Subclass NSView. A new item is added to the list, with its name highlighted. Type in a descriptive name for the class. If you are using XCode 3.0 or greater, Apple changed the way to do this step. In XCode, right click in the folder "Other Sources", then mouse over "Add", then "New File...". Under the Cocoa heading, choose "Objective-C Class" from the icon list, then in the drop down list below that area, select "NSView subclass". Type in your name in place of untitled, and leave the other stuff alone. I used 'GCHelloView' because GC are my initials, and HelloView is reasonably descriptive. Avoid the temptation to use silly or obscure names — you'll regret it later!
Now go back to the window and click on CustomView to select it. Choose Tools→Show Identity Inspector. A new palette appears, which allows you to set the properties of objects. In the pop-up menu, choose "Custom Class". A list appears. Find your custom class name ('GCHelloView' in my case) and select it. You'll see that the name of the view in the window changes to this name.
The next step is to get IB to actually create some skeleton code for this new class. Go back to the main window and its "Classes" panel. Ensure your custom view class is selected, and choose Classes→Create Files for GCHelloView (or whatever you named it). A dialog opens asking whether you want the files added to the project. Of course you do, so the defaults are fine. Click Choose. The files are created and added to Xcode automatically. If you're using a version of XCode larger than 3.0, however, don't worry — you created these files earlier during the process of adding them. We're done with IB for now, so save the changes, and return to Xcode.
In the left-hand panel of your Xcode project, drill down to HelloWorld/Other Sources. You'll see two files added there: GCHelloView.h and GCHelloView.m (or whatever name you chose). The .h file is the header, as with any C program. The .m file contains the Objective-C code. Select the .h file. The main area of the Xcode window changes and displays the content of this file. (By the way, you might like to make the text part bigger by dragging the split bar upwards — the top area isn't very interesting here and can be made much smaller.) The interesting part looks like:
@interface GCHelloView : NSView { } @end
This is how a subclass is defined in Cocoa — everything between '@interface' and '@end' is the class definition. The class name is GCHelloView, and it is a subclass of NSView. Because our class doesn't implement any new methods, it will just reimplement (or override, as we call it) existing NSView methods, the definition is empty and we don't need to add anything here. Note: C++ programmers might find this a bit odd — in C++ we must explicitly declare which methods we are going to override; in Objective-C we just do it, which saves ourselves a little effort.
OK, to the code! Select the .m file. You will see two method skeletons there, one called 'initWithFrame:', and one called 'drawRect:' (remember, a colon is an intrinsic part of a method's name). For this exercise, we don't need to worry about the initWithFrame: method; all of our code will go in the drawRect: method.
First though, what happens if we build and go now? Try it! You'll see that everything compiles just fine, the program runs, and the window is blank once more (assuming you took the old text out in IB). OK, quit and go back to the editor.
This time, we want to make the text we write in the window a lot more impressive, so we'll make it use a large font size and a bright color. The way that Cocoa specifies fonts and so forth when rendering strings is to use a dictionary of attributes. This is an object which simply associates a named property with a value. The font property is named "NSFontAttributeName", and the color of the text attribute name is "NSForegroundColorAttributeName". These names are called the keys of the attributes. First we'll set up these attributes, then draw the string using them. Here's the code:
- (void)drawRect:(NSRect)rect
{
NSString* hws = @"Hello World!";
NSPoint p;
NSMutableDictionary* attribs;
NSColor* c;
NSFont* fnt;
p = NSMakePoint( 10, 100 );
attribs = [[[NSMutableDictionary alloc] init] autorelease];
c = [NSColor redColor];
fnt = [NSFont fontWithName:@"Times Roman" size:48];
[attribs setObject:c forKey:NSForegroundColorAttributeName];
[attribs setObject:fnt forKey:NSFontAttributeName];
[hws drawAtPoint:p withAttributes:attribs];
}
Type that in exactly as given, and hit "Build and Go". Your application should greet you in large red letters! (If the text doesn't show up, try making the "GCHelloView" box in IB larger.) This code might look a little complex for such a trivial task as drawing a large red string, but in some ways that's because this task is a little artificially contrived. However, it's not that complex, let's look at it line by line.
The top five lines are simple variable declarations as you'd find in any C routine. The pointer variables with the name 'NS…' indicate that these are references to Cocoa objects. We declare a simple fixed string first, containing the text "Hello World". This is an NSString object. This object is very different from an old-style C string, though here we use it in a very similar way. NSStrings are objects containing a text string which is encoded in a particular way, such as Unicode. NSStrings are more than mere containers, however; they have many methods for performing operations on strings. We'll revisit it later, but for now we just use it in a fairly dumb manner. Note that NSString constants are preceded by the '@' symbol and surrounded by quote marks.
The attributes are stored in a NSDictionary object which here is mutable — that is, we wish to change it so that we can set up the attributes, so we use an NSMutableDictionary object. The color and font information is encapsulated in NSColor and NSFont objects.
Note that NSPoint is not a class but a plain old C type. It is a struct consisting of two floats x and y representing coordinates. That is why p is not a pointer variable like hws or attribs.
Now we will tell the text WHERE it will be drawn within the window. 'p' is just a coordinate which is 10 pixels in from the left, and 100 pixels up from the bottom of the view.
Next we instantiate the dictionary object — that is, we make a concrete object of the class NSMutableDictionary. The line that does this is one you will encounter again and again — the first method alloc reserves memory for the object and returns it; the second one, init, asks it to initialise itself (whatever that means in practice); and the third tells it to "autorelease" itself. We'll discuss what that means later. Hold on, you might be saying — what object is being called with the alloc method? We haven't made the object yet! In fact, alloc is an example of a class method, which is a method that doesn't require a concrete example of the object, the class itself knows what to do. Class methods are common — we'll encounter two more in a minute — and very useful. They allow us to perform generic operations that pertain to the class of object without actually having an object available. Very often these class methods return actual objects — they are known as factory methods in OOP-speak. A class method is called very simply by using the name of the class in place of an object instance variable, e.g.
red = [NSColor redColor]; /* class method */
is a class method but:
bright = [red brightnessComponent]; /* instance method */
is an instance method.
Two more factory methods follow — the first to make an object embodying the color red, the second a font object embodying the font 48 point Times Roman. These are then added as attributes to the dictionary using the appropriate keys.
Finally, we are all set and ready to draw. The last line accomplishes this. Note that it is a method of NSString itself — the string knows how to render itself, provided we give it enough information. So we tell it where, and what attributes we'd like it to use. Note that what we didn't tell it was WHEN to draw the string. Instead, we just made the method available, knowing that Cocoa would call upon it when required. Cocoa will do this when it needs to refresh the content of the window — at such times, the documentation tells us that a view's drawRect: method will be called, so provided we put our code there, it will get called as needed.
Gilding the lily
editLet's add a little more code, just for fun. At the end of the method, before the closing brace, add:
[[NSColor blueColor] set]; NSFrameRect([self bounds]);
Hit Build & Go (command-R), and see what that does. Maybe you can guess?
The first line is a class method on NSColor to get us a blue color object, then call its 'set' method. This simply establishes that color as the one that will be used for drawing whatever happens to be drawn next. This is a handy way of setting up colors, and you will use it often. Finally we use a simple C function, 'NSFrameRect' which draws a rectangular frame. The interesting thing here is that the rectangle it draws is a property of the view itself, called bounds. To ask an object for one of its own properties, or to call one of its own methods we use the object reference 'self'. This always refers to the object in which the code itself resides, so it's a very handy way of talking to yourself. The bounds of a view is the space it occupies within its parent view or window.
Previous Page: Getting around in Xcode and Interface Builder | Next Page: Objective C, the language and its advantages