Chapter 4. Thinking in Objects

A funny thing happened on the way to the future. Computers became much more powerful, and developers decided to take advantage of this potential by making software that did a lot more than just process one instruction at a time on the command line.

They built more sophisticated interfaces that could do a lot of things at once. This allowed software to tackle complex tasks like nonlinear video editing and 3D modeling. But creating programs with such sophisticated user interfaces required a lot of code.

Expert engineers love simplifying code. They’re obsessed with it. Good programmers think of ways to write code more quickly, but great engineers think of ways to write less code to do the same job. There’s a good reason for this. The less code you have, the easier it is to understand and improve.

The functions in earlier chapters do a calculation and return a result. You store the result in a variable, and then pass it into another function, like this:

#include <stdio.h>
#include "types.h"

main () {
    
    char* itemType     = "Document";
    int   itemCount    = countForItemType      ( itemType );
    int   itemTotal    = totalForCountOfType   ( itemCount, itemType );
    char* totalString  = formattedTotalForType ( itemTotal, itemType );

    printf ("The total for %s is %s", itemType, totalString);
    free   ( totalString ); 
}

C is a procedural language, which means that functions and the data are separate, and the code is fairly linear. You can also say that C is a static language. This means that all functions have to exist before you call them, and when you call a function, it will run. There’s no reinterpretation as the program is running. Also, all variables have to be a specific type, which is called static typing.

The upside of this is that the code is predictable and you have complete control. The downside is that you have manage all of the details manually, which means you end up doing a lot of busywork. This is hard to picture when working with very small programs, but it’s very clear when working with programs that are made up of hundreds of thousands or millions of lines of code.

Tip

I think of this as a CEO of a 2,000-person company answering support calls. He may be good at it, but it’s probably better for everyone if he runs the company instead. You are the CEO of your application. If you spend time on low-level details, you may never be able to take on the bigger tasks. Instead, you delegate the lower-level details to Cocoa.

So engineers started trying to think up better ways to program, and two ideas that took hold are object-oriented programming and dynamic languages. Standard C had the seeds of object-oriented concepts with structs. Each struct definition is a template for a certain kind of thing, like a movie or a document. You create a variable of that struct type, and you have another instance of the struct with its own set of values. From that single struct, you can store data for any number of different movies.

By convention, developers started writing functions that used structs instead of individual char and int variables. You did this with the createSong() and displaySong() functions in the previous chapter. Objective-C took this convention and formalized it.

Objective-C also added dynamic features, which means that not everything in your code has to be defined when you compile. You can declare “typeless” variables and can use classes and methods that don’t exist yet. This allows the design of the application to be more flexible, making it much easier to implement things like plug-in support. A lot of the thinking behind Cocoa is based on the dynamic features of Objective-C.

Structs and Classes

Because you already know how C works from the earlier chapters, the easiest way to introduce object-oriented concepts is to compare them to structs and functions.

I’m not going into detail about the Objective-C language in this chapter. I’m just using it as a way to show you object-oriented concepts. This means you will see parts that don’t make sense yet, such as the @ symbol and square brackets. I’ll cover these and the rest of the language in detail in the following chapters. For now, just focus on the concepts.

The Song.h file from Chapter 3 looks like this:

typedef struct {
  char* title;
  int   lengthInSeconds;
  int   yearRecorded;
} Song;

Song createSong  ( char* title, int length, int year );
void displaySong ( Song theSong );

In Objective-C, the equivalent Song.h file looks like this:

@interface Song : NSObject 1

  @property char* title; 2
  @property int   lengthInSeconds;
  @property int   yearRecorded;

+ (Song *) song; 3
- (void) display;

@end

This is called a class, and you may notice that it looks a lot like a struct. This class interface is the part of the class that goes in the header file. It’s a lot like declaring structs and functions in a header file. The class implementation goes in a separate file.

1

This line declares the class name and defines which class it inherits from (see Inheritance).

2

Instance variables or properties are very similar to fields in a struct. I’ll describe properties in more detail in the following chapters; for now, just know that the intent of struct fields and class properties is the same: to store a unique set of values for each instance.

3

Methods are similar to functions, though methods are not freestanding. Each method belongs to a class. That means that two classes can have display methods that do completely separate things. Methods have direct access to the class’s properties, so you don’t need to pass any of those values in when calling a method.

I used a char* C string in this example because you’re already familiar with it, but you won’t see this in the source code for most Cocoa applications. You can use C strings in Objective-C, but Cocoa has its own type for strings, which I’ll describe in the following chapters. Other than this one detail, though, this is very much like a class you’d find in any Cocoa application.

Each instance of a class is called an object. Even though you have only one Song class (the blueprint), you can have many Song objects. Here’s how you make a Song struct in C code, compared to making a Song object in Objective-C code:

// creating a Song struct in C.
Song mySong;
mySong.title           = "Hey Jude";
mySong.lengthInSeconds = 425;
mySong.yearRecorded    = 1968;
displaySong ( mySong );

// creating a Song object in Objective-C.
Song* mySong           = [Song song];
mySong.title           = "Hey Jude";
mySong.lengthInSeconds = 425;
mySong.yearRecorded    = 1968;
[mySong display];

Tip

The rules about including Objective-C class headers are the same as including C headers with struct definitions. If you refer to a class literally in code, you have to include its header file. If you don’t include the file, you will see a build error.

The Song object variable is a pointer. All object variables in Objective-C are pointers because they’re always stored in dynamic memory—just as if you had allocated them with the malloc() function. In fact, deep down in Objective-C, that’s exactly what’s happening.

Designing Classes

At first, classes look like a way to group functions and variables together. That’s technically true, but not really the main point. The idea is that if you want to describe something like a photo, it’s easier to create a Photo class with all of the methods and properties related to that concept rather than a series of structs and functions that simulate a photo class.

When I first create a class, I don’t think about which methods (functions) it will have. Instead, I focus on the kind of data it needs to store. So if I’m designing a Game class, I know it will need at least two player names, two scores, and a game duration. So I just start with that:

@interface Game : NSObject

  @property char* playerOne;
  @property char* playerTwo;
  @property int   playerOneScore;
  @property int   playerTwoScore;
  @property int   durationInSeconds;

@end

A class isn’t just a big box to put a bunch of things in. Novice programmers may be tempted to do something like this, because it seems convenient to put everything in one class:

@interface Game : NSObject

  @property char* playerOne;
  @property char* playerTwo;
  @property int   playerOneScore;
  @property int   playerTwoScore;
  @property int   durationInSeconds;


  // these are not part of a 'Game' so they're not appropriate for this class.
  @property char*  computerName;
  @property int    cpuCount;
  @property char*  userName;
  @property char*  userPassword;

@end

The problem with this is that the computer name, CPU count, user name, and user password have nothing to do with the Game class. Even if you use them in the same application, they don’t belong in the same class. Instead, create a separate class for each distinct kind of data:

@interface Game : NSObject

  @property char* playerOne;
  @property char* playerTwo;
  @property int   playerOneScore;
  @property int   playerTwoScore;
  @property int   durationInSeconds;

@end

@interface Computer : NSObject

  @property char*  name;
  @property int    cpuCount;

@end

@interface User : NSObject

  @property char*  name;
  @property char*  password;

@end

If each kind of data is separate from the others, the overall design of your application is much more flexible. You can change how the Computer class works without affecting the Game class or the User class. This is very important when creating Mac and iPhone apps.

Accessors

In the previous chapter, I described how encapsulation is a way to separate the what from the how. In one example, I showed you how adding a printf() call inside the createSong() function allowed you to automatically display the Song instance when it’s created.

Accessors are “gatekeepers” for properties. If you use accessors, the details of how the data is stored is separate from what the data is. For example, here is a pair of accessor methods for a title property of the Photo class:

- setTitle: newTitle {
  title = newTitle;
}

- title {
  return title;
}

The setTitle: accessor method takes an input value and assigns it to the instance variable (the actual variable that stores the property data). This is called a setter accessor method. The title method is the getter accessor method, and it simply returns the instance variable’s value.

Tip

In other object-oriented languages, it’s more common to have a getValue method and a setValue method, but Objective-C methods rarely have get as a prefix. It’s used only when returning a value through indirection.

Now imagine after you wrote this code, you decided to store all your data in a database. Instead of going and changing every part of the program that sets or gets a photo’s title, you can just change the accessors in one place:

- setTitle: newTitle {
  saveTitleValueToDatabase(newTitle);
}

- title {
  return titleValueFromDatabase();
}

Software changes a lot. One of the best things you can do as a programmer is make it easy to change the code. Even though you start out with an idea of how your program will work, you will probably change your mind later. It’s often hard to “see” the whole thing in your head before you start (though you’ll get better at this).

Good code is written with a pencil: easily changed. Bad code is written in ink: difficult to change. If you need to change good code, you can just erase a few things and continue on. If you need to change bad code, you’re often better off just throwing it out and starting with a blank slate.

Inheritance

One of the key concepts in object-oriented programming is inheritance, which allows you to pull in properties and methods from a “parent” class, also known as a superclass. For example, if you have both a Song class and a Movie class in your application, it might make sense to create a Media superclass that both Song and Movie inherit from, because they’re both logically an extension of a more generic “media” type. They can then share instance variables and methods:

@interface Media : NSObject

  @property int     duration;
  @property char*   format;

  - (void) play;
  - (void) pause;
  - (void) rewind;

@end

All of these things make sense for both Song and Movie objects, but each class can also add its own functionality. For example, Movie might add properties like aspectRatio and framesPerSecond, as well as a method called enableSubtitles. The new class, including inherited methods, would look like this:

@interface Movie : Media

  @property float aspectRatio;
  @property int   framesPerSecond;

  - (void) enableSubtitles;

  // inherited from Media.

  @property int   duration;
  @property char* format;

  - (void) play;
  - (void) pause;
  - (void) rewind;

@end

Tip

You usually don’t redeclare methods or properties that you inherit from a superclass. I added the declarations here for clarity.

In this example, Movie is a subclass of Media. Another class could be a subclass of Movie, bringing in everything from both the Movie class and the Media class. The whole tree is called a class hierarchy. Most frameworks have a “root” object that all other classes inherit from. Cocoa actually has two root classes: NSObject and NSProxy, but almost all of your classes will inherit from NSObject.

Tip

Some object-oriented languages have multiple inheritance, which allows you to inherit from more than one class. Not everyone agrees on whether this a good or bad thing, though, and the creators of Objective-C decided not to include it in the language.

In addition to adding methods, subclasses can also override methods from a superclass. It’s common to override methods when you want to customize existing behavior. For example, if you want to use a standard Mac OS X button in your application, but would like to change the clicking behavior, you could create a subclass of NSButton and override the methods related to clicking.

You don’t use superclasses just to share code, though. A subclass should only inherit from a class that it’s a logical extension of. For example, you shouldn’t create a subclass of Media called Camera, because a camera is not a kind of media. Instead, you could make a generic Device class, and create subclasses for Camera, Phone, and so on.

Composition

Classes can have properties that refer to instances of other classes. This is called composition, because you’re combining different kinds of objects together to accomplish a task:

@interface User : NSObject

  @property char*  name;
  @property char*  password;

@end

@interface Game : NSObject

  @property User* playerOne;
  @property User* playerTwo;
  @property int   playerOneScore;
  @property int   playerTwoScore;
  @property int   durationInSeconds;

@end

When you want to use methods and properties from another class, but it logically doesn’t make sense to inherit from that class (a camera isn’t a kind of media), you can create a property for the other kind of object instead:

@interface Device : NSObject

  @property char* name;
  @property int   minFocalDistance;

@end

@interface Media : NSObject

  @property int     duration;
  @property char*   format;
  @property Device* recordingDevice;

@end

This way, a Media object can use a Device object, but it doesn’t have to be a Device object, which makes much more sense. Cocoa encourages you to use composition much more frequently than subclassing, because subclassing can add complexity, which means it’s harder to create great software.

Object Lifetime

The last building block concept is object lifetimes. Just like in C, you need to clean up the memory of objects when you’re no longer using them. The actual process of reserving and freeing memory in Objective-C is easier than in C, but the trade-off is that there are usually more cleanup tasks.

In Objective-C and most other object-oriented languages, a class almost always has an initialization method. If you don’t add one to your class, you typically inherit it from the root object. You create an initialization method when you want to set up default values for things. For example, you might want to set the initial title value for a Photo object to “Untitled Photo” so it’s not just blank:

- init {
  title = "Untitled Photo";
}

The counterpart to the initialization method is a “cleanup” method, which you create if you want to clean up memory for any data you have, or maybe display a message in the console that the object is shutting down:

- dealloc {  
  printf ("Deallocating Photo\n");
}

There are some more advanced techniques in Cocoa that we will use the cleanup methods for, but you need to learn about Objective-C before we can go into that in detail.

Built-in Classes

Most of Cocoa is available to you as classes. Cocoa has hundreds of different classes that do things like display web pages, record video, create PDFs, and search for files on the user’s computer. The process of becoming a better Cocoa programmer is really about learning how these classes work. There are some C functions and structs, too, but Cocoa is designed as an object-oriented framework.

For example, I can make a window object using the NSWindow class (I’ll tell you more about windows in Chapter 8). In three lines of code, I can create a window object, set its size and position, and display it on the screen:

id myWindow = [[NSWindow alloc] init];    
[myWindow setFrame:NSMakeRect(100, 100, 400, 300) display:YES];
[myWindow orderFront:nil];

I can also set properties on the window, like the title:

myWindow.title = @"An Empty Space";

Once I’ve done that, I can see the window on the screen (Figure 4-1).

A window object displayed on the screen
Figure 4-1. A window object displayed on the screen

A lot of the code you write in Objective-C is very similar to this. You create an object, set some properties on it, and then the user can start interacting with the object on screen. I’ll introduce you to many different Cocoa classes as you continue through the book.

Get Cocoa and Objective-C: Up and Running now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.