Chapter 1. Using Objective-C's Extensions to C for iPhone Development

In This Chapter

  • Knowing how Objective-C works

  • Understanding objects and classes

  • Getting the naming conventions down pat

  • Seeing how objects are created

  • Checking out the standard way to do initialization

  • Working with declared properties

Objective-C is an object-oriented programming language, which means that it was created to support a certain style of programming. Yes, there are many different styles, but unless you're a dyed-in-the-wool member of a particular camp, it's really unnecessary to get into that discussion now (or probably ever). Objective-C has its fans and its detractors, but my advice is to ignore both sides and get on with your development. There are some things I really like about the language, and others I don't, but in essence, it is what it is, and it is what you'll use.

You can pick up quite a bit about object-oriented programming in this book, but if you want a deeply intimate understanding of it, get Neal's Objective-C For Dummies (a shameless plug). After you read that book, you'll probably wonder why anyone would ever want to program in any other way.

As you might guess, object-oriented programs are built around objects — no surprise here. But a word to the wise: Keep your objects as ignorant as possible about the environment in which they work and the other objects they use. This is called encapsulation — an object hides all the details of how it works while allowing other objects to use its methods. Encapsulation makes it easier to enhance or extend an object's capabilities without disturbing how other objects use it (I explain why this is important in Chapter 2 of this minibook). Although there will always be some dependency whenever one object uses another, limit those dependencies to what other objects do rather than to how they do it, and limit the number of objects each one uses.

Similarly, avoid the compulsion to create switch statement control structures that determine the order in which objects get called and that dole out instructions to them. The best object-oriented programs have their objects work like a team, with everyone playing their roles and doing their part rather than submitting to the traditional hierarchical command and control structure where someone is in charge and tells everyone else what to do.

Sergeant Schultz of Hogan's Heroes captured the spirit of object-oriented programming with his trademark line:

"I hear nothing, I see nothing, I know nothing!"

What You Need to Know About Objective-C

You'll use Version 2.0 of the Objective-C language, which was released with Mac OS X 10.5, and yes, you should care. Version 2.0 has some new and very useful features — such as fast enumeration, which generates more efficient code, and a new syntax to declare instance variables as properties, with optional attributes to configure the generation of accessor methods (I describe declared properties in "Working with Declared Properties" in this chapter).

But it takes more than a language to write a program; it takes a village. So who lives in the Objective-C village? Most object-oriented development environments consist of several parts:

  • An object-oriented programming language

  • A runtime system

  • A framework or library of objects and functions

  • A suite of development tools

One of the defining features of Objective-C is its runtime system, which is linked to your program. It acts as a kind of operating system (like the Mac or iPhone OS) for an individual Objective-C program. This runtime system is responsible for making some of the very powerful features of Objective-C work.

The framework you use is called Cocoa. It came along with Objective-C when Apple acquired NeXT in 1996 (when it was called NextSTEP). Cocoa lets you write applications for Mac OS X and apps for the iPhone. If the operating system does the heavy lifting vis-à-vis the hardware, the framework provides all the stuff you need to make your program an application. It provides support for windows and other user-interface items as well as many of the other things that are needed in most applications. When you use Cocoa, developing your application is way easier because all you need to do is add the application's specific functionality — the content as well as the controls and views that enable the user to access and use that content — to the Cocoa framework. (I introduce frameworks in Chapter 5 of Book I.)

The Objective-C runtime environment also makes it possible to use tools like Interface Builder to create user interfaces with a minimum of work, (I show you how to use Interface Builder in Chapter 2 of Book III.) The two main development tools you use are the aforementioned Interface Builder and Xcode, both of which I introduce in Chapter 4 of Book I and show you how to use in Book III.

Introducing Objects and Classes

If you already know something about programming in C or C++, you are part of the way toward understanding Objective-C. For simplicity's sake, let's just say that the style of programming with C is procedural — a program is understood as a list of tasks (functions or subroutines) to perform on a separate data structure (which is a collection of variables under a single name).

The programming style with C++ is a mix of procedural and object-oriented: You can use objects that are defined by classes.

Note

When I use the word class, I'm talking about code that you write, and when I use the word object, I'm talking about behavior at runtime. Whereas a class is a structure that represents an object's type, an object is something that exists in a computer's memory. An object is an instantiation (big computer science word here) of a class. In more down-to-earth terms, a class is a type, and an object is like a variable.

An object brings together the data structure and the methods that process the data in the structure. Each object is like an independent machine with a distinct role, and capable of receiving messages, processing data, and sending messages to other objects. C++ objects tend to be small and dependent on one another — each and every concept, no matter how small, gets its own class. Applications typically have hundreds if not thousands of classes, each one small, and all interconnected. C++ objects tend to come in groups — each time you want to include functionality from an object, you likely need to include an additional 10 to 20 objects.

The programming style of Objective C is to create objects that encapsulate the details of how they work. Classes are different between C++ and Objective C, especially in the declaration. In C++, each class is a structure, and variables and/or methods are included in that structure. In Objective-C, variables are in one section of the class, and methods in another. The methodology focuses on data rather than processes, with programs composed of self-sufficient objects, each containing all the information needed to manipulate its own data structure.

To understand the different programming styles, imagine a vacation budget app that keeps track of credit card transactions in a foreign currency and tells you how much each transaction is in U.S. dollars. It also keeps track of spending against a budget you can set at the beginning of a trip. (If you read Neal's Objective-C For Dummies, you can find out how to build this application step by step.)

In a procedural language, you might define a data structure and set of functions for a particular country and then modify them for other countries. But adding new functions for each country would be a lot of work, and it also seems like a waste because the functions all are basically the same — just operating on a different data structure. And, as you can imagine, adding more countries would require coding and testing new functions, and the project would quickly get out of hand.

But more importantly, if you ever wanted to change the data structure, you would have to go out and find all the functions that used it and change them also. If you wanted to have a different kind of budget for New Zealand, for example, one where you tracked your wool purchases, you would either have to add that to all the countries you visited, even though you didn't use it anywhere except New Zealand. Or you would have to create a special data structure for New Zealand and rewrite the functions to use the new data structure. If you then needed to go back to make a change to the original data structure for any reason, you would have to remember to change both, as well as all the functions that used them.

If you can't claim a photographic memory as one of your personal strengths, you've got a problem. Objects (and classes) provide the solution. An object packages together data with the particular operations that can use or affect that data.

A class definition is like a structure definition in that it defines the data elements (which are called instance variables) that become part of every instance. But a class expands the idea of a data structure — containing both data and functions instead of just data. Functions, however, become methods that both specify and implement the behavior of a class.

The class definition is a template for an object; it declares the instance variables that become part of every object of that class and the methods that all objects of the class can use.

Each object (an instance of a class) has memory allocated for its own set of instance variables, which store values particular to the instance.

When you create an object from a class, you're essentially establishing an area in memory land that will hold the object's instance variables. But while every object has its own instance variables, all objects of that class share a single set of methods.

One more thing — in Objective-C, classes have two parts:

  • An interface that declares the methods and instance variables of the class and names its superclass (Don't worry. I explain all that.)

  • An implementation that actually defines the class — the code that implements its methods

These two parts are almost always split between two files (although there can be more), but to make things easier, I postpone doing that until later, in "Spreading the wealth across files" later in this section.

Grasping objects and their classes

Once again, imagine the vacation budget app. You might create two functions, spendDollars: and chargeForeignCurrency:, that track your spending and foreign currency conversion charges against budgets for different countries. Rather than hard-coding the data for all the countries in one budget, you might use a different budget (such as europeBudget and englandBudget) for each country, and then use a pointer to a budget variable. A pointer's value is a memory address — it refers directly to (or "points to") another value stored elsewhere in memory. Using this pointer, the functions can access different budgets depending on where you were (Europe or England), and the function would operate on the data for that country.

The budget data structure definition would look like the following (I omitted the function implementations):

typedef struct {

  float exchangeRate;
  double budget;
  double exchangeTransaction;
} budget;

void spendDollars (budget *theBudget, double dollars);
void chargeForeignCurrency (budget *theBudget,
                               double foreignCurrency);

The problem is that if you wanted to change the data structure struct, you would have to go out and find all the functions that used it and change them. Although in a program this small it would be simple (there are only two functions, after all), in a more complex program, there could be functions all over the place using the struct.

A class that provides the same functionality as the budget struct data structure and the functions that use it, spendDollars: and chargeForeignCurrency:, would look like this:

@interface

Budget : NSObject {

  float  exchangeRate;
  double budget;
  double exchangeTransaction;
}

- (void) spendDollars: (double) dollars ;
- (void) chargeForeignCurrency: (double) foreignCurrency;

@end

Data and functions are now both members of the object. You no longer need to track down all the functions that use a particular data structure and revise them. Object-oriented programming allows you to skate right by such headaches through encapsulation. You no longer use sets of global variables or data structures that you pass from one function to another as arguments. Instead, you use objects that have their own data and functions as members.

Note

Operations (or functions) are known as the object's methods; the data they affect are its instance variables. In essence, an object bundles a data structure (instance variables) and a group of functions (methods) into a self-contained programming unit. You then ask an object to do something for you — such as subtract the amount you just spent from your budget — by sending it a message. When an object receives a message, it then executes the code in the appropriate method.

This encapsulation solves the problem of the widespread impact that changing a data structure may have. Only an object's methods that are packaged with the data can access or modify that data, although an object can — and often does — make its data available to other objects through its methods.

Scoping instance variables

Variables are not accessible from every nook and cranny in your program — they're accessible only within the function in which they are declared (that is, within the braces). This is also referred to as being scoped to the function. You could also have braces (which define a block) within a function, in which case variables are scoped within that code block.

Note

A code block is a group of statements grouped together and enclosed in braces: { }.

Instance variables are scoped to (accessible within) the code block they're in. This can be a function, a code block within a function, or, in this case, a class. It is this built-in scoping mechanism that allows an object to hide its data by essentially limiting who (or what) has access to it. But to provide flexibility, when it comes to a class (here come the Objective-C extensions to C again), you can actually explicitly set the scope to three different levels through the use of a compiler directive:

  • @private: The instance variable is accessible only within the class that declares it.

  • @protected: The instance variable is accessible within the class that declares it and within classes that inherit it. This is the default if you don't specify anything.

  • @public: The instance variable is accessible everywhere.

Warning

Don't use @public! If you do — go directly to jail, do not pass Go, and do not collect $200. If you have to ask why, reread the first part of this chapter.

Spreading the wealth across files

You may have started your coding in a single source file — perhaps MyFirstProgram.m and then moving on to Budget.m (for the vacation budget app) or YourApp.m. Although this works initially, it won't scale when you start to develop a real app. As your program gets larger, scrolling through a single file becomes more difficult. But there's a well-thought-out solution for that problem that just about everyone uses.

When I write even the simplest apps for the iPhone, I divide things into multiple files.

The source code for Objective-C classes is divided into two parts. One part is the interface, which provides the public view of the class. The @interface contains all the information necessary for someone to use the class. The other part of a class's source is the implementation. The @implementation contains the method definitions.

Note

Because of the natural split in the definition of a class into interface and implementation, a class's code is often split into two files along the same lines. One part holds the interface components: the @interface directive for the class and any enum, constants, #defines, and so on. Because of Objective-C's C heritage, this typically goes into a header file, which has the same name as the class with an .h at the end. For example, the class MainView header file is called MainView.h. (You can see this file when you select the Utility Application template in Xcode, as I describe in Chapter 4 of Book I.)

Note

All the implementation details, such as the @implementation directive for the class, definitions of global variables, the method definitions (implementations), and so on go into a file with the same name as the class and with an .m at the end. MainView.m would be the implementation file for the MainView class. (You can see this file when you select the Utility Application template in Xcode, as I describe in Chapter 4 of Book I.)

Knowing the naming conventions

It's helpful to have some idea about how to name things in order to avoid having the compiler scream at you. Here are some areas you need to pay attention to:

  • The names of files that contain Objective-C source code have the .m extension. Files that declare class and category interfaces or that declare protocols have the .h extension typical of header files. (A category is used to extend a class; I explain that along with protocols in Chapter 5 of this minibook.)

  • Class, category, and protocol names generally begin with an uppercase letter; the names of methods and instance variables typically begin with a lowercase letter. The names of variables that hold instances also typically begin with lowercase letters.

  • In Objective-C, identical names that serve different purposes are allowed and are resolved at runtime. For example:

    • A class can declare methods with the same names as methods in other classes.

    • A class can declare instance variables with the same names as variables in other classes.

    • An instance method can have the same name as a class method.

    • A method can have the same name as an instance variable.

    • Method names beginning with _, a single underscore character, are reserved for use by Apple.

    • However, class names are in the same name space as global variables and defined types — the name space uniquely identifies a set of names so that there is no ambiguity when objects having different origins but the same names are mixed together. As a result, a program can't have a defined type with the same name as a class.

Note

Objective-C is case-sensitive. Budget and budget are not the same thing — Budget is a class, and budget is a variable.

Using id and nil

As part of its extensions to C, Objective-C adds two built-in generic C types that you can use for objects.

id is a generic C type defined as a pointer to an object data structure, which you use to refer to any kind of object regardless of class. All objects, regardless of their instance variables or methods, are of type id. You use id when I explain protocols in Chapter 5 of this minibook. For now, just keep this in mind.

Similarly, nil is defined as a null object, an id with a value of 0.

Note

id, nil, and the other basic types of Objective-C are defined in the objc/objc.h header file.

Declaring the Class Interface

The purpose of the class interface is to give users of a class the information they need to work with the class. The declaration of a class interface begins with the compiler directive @interface and ends with the directive @end. (All Objective-C compiler directives begin with @.)

@interface ClassName : ItsSuperclass {
  instance variable declarations
}
method declarations
@end

In the interface, you specify the following:

  • The class's name and superclass:

    @interface ClassName : ItsSuperclass {

    A class can be based on another class called its superclass, and it inherits all the methods and instance variables of that class. I explain all about inheritance in Chapter 2 of this minibook.

  • The class's instance variables: Instance variables correspond to the members (variable declarations) in a data structure (struct).

  • The class's methods: Methods are a group of functions or operations specific to a particular class.

For example, here's the class interface for the Budget class shown in the section "Grasping objects and their classes" earlier in this chapter:

@interface Budget : NSObject  {

  float  exchangeRate;
  double budget;
  double exchangeTransaction;
}

- (void) createBudget: (double) aBudget
               withExchangeRate: (float) anExchangeRate;
- (void) spendDollars: (double) dollars ;
- (void) chargeForeignCurrency: (double) foreignCurrency;
@end

As you can see, the interface has four parts, which appear in this order:

  1. The @interface compiler directive and first line

  2. The instance variables

  3. The methods

  4. The @end compiler directive

By convention, class names begin with an uppercase letter (such as Budget); the names' instance variables and methods typically begin with a lowercase letter (such as exchangeRate: and spendDollars:).

The next few sections take a closer look at each one of the four parts.

The @interface compiler directive and first line

The @interface compiler directive tells the compiler that you're declaring a new class. It's the first line in the first example in the previous section:

@interface Budget : NSObject {

Budget : NSObject declares the new class name and links it to its superclass.

: NSObject on the @interface line tells the compiler that the Budget class is an extension of the NSObject class — NSObject is the Budget class's superclass, in other words. Budget will inherit all the methods and instance variables of NSObject. This means that, for all practical purposes, even though you don't see them in your class declaration, Budget includes all the instance variables and all the methods that are in NSObject.

Note

NSObject is a root class that defines the basic framework for Objective-C objects and object interactions. It imparts to the classes and instances of classes that inherit from it the ability to behave as objects and cooperate with the runtime system. Because Budget inherits from NSObject, it has all the functionality an Objective-C object would need at runtime.

The instance variables

After starting to declare a new class, you tell the compiler about the various pieces of data you want to work with — the instance variables and methods.

In my (running) example, the following lines of code appear on the line after

@interface Budget : NSObject { :

  float  exchangeRate;
  double budget;
  double exchangeTransaction;
}

exchangeRate, budget, and exchangeTransaction are the instance variables for objects of class Budget.

The reason they're called instance variables is that when you create an object of the Budget class, you're creating an instance of the class, which means that for each object you create, you allocate some amount of memory for its variables (just as you do for the data structure). The instance variables here correspond to the ones you would have used in the struct (data structure):

  • exchangeRate is the current, well, exchange rate — the number of dollars it will cost you to get one euro or one pound, for example.

  • budget holds the amount of dollars you have left to spend in a given country.

  • exchangeTransaction is the amount in U.S. dollars of a foreign currency transaction.

Because budget, exchangeRate, and exchangeTransaction are declared in the class definition, every time a Budget object is created, it includes these three instance variables. So every object of class Budget has its own budget, exchangeRate, and exchangeTransaction. The closing brace tells the compiler you're done specifying the instance variables for Budget.

The methods

Keeping the focus on the running example, the following lines of code appear on the line after the brace (}):

- (void) createBudget: (double) aBudget
                withExchangeRate: (float) anExchangeRate;
- (void) spendDollars: (double) dollars ;
- (void) chargeForeignCurrency: (double) foreignCurrency;

In Objective-C, these lines of code are called method declarations. They make public the behavior that Budget has implemented — making it clear to the whole world that this is the kind of stuff any object of the Budget class can do.

Methods are like functions that return a value, and method declarations are like function prototypes in C or C++, (although they look different). A function prototype is a declaration of a function that omits the function body (its definition), but specifies its name so that you can start referring to the function in your code. Eventually you have to provide a function definition elsewhere in the program in order to actually use the function. The function prototype also specifies the data type (the kind of data, such as char, int, and float, to be stored in memory) of the function's arguments, and the data type of the value that the function returns.

Here is an example of a method declaration (from the preceding code):

- (void) spendDollars: (double) dollars;

The leading dash signals that this is the declaration for an Objective-C method. That's one way you can distinguish a method declaration from a function prototype, which has no leading dash.

Following the dash is the return type for the method, enclosed in parentheses — the value of what the method returns (similar to what a function returns). Methods can return the same types as functions, including standard types (int, float, and char), as well as references to other objects.

spendDollars: is a method that takes a single argument of type double. Notice that instead of the parentheses used in a function to indicate arguments, methods use a colon (:). Also notice that the colon is part of the method name.

Note

Another difference between function prototypes and a method declaration is that in a method declaration, both the return type and the argument type are enclosed in parentheses. This is the standard syntax for casting one type as another — converting from one data type to another.

Although the spendDollars method I came up with doesn't return a value, I could add that functionality at a moment's notice by doing just what you'd do if you were dealing with a function prototype — adding the following:

return someValue;

For all practical purposes, chargeForeignCurrency works the same way as spendDollars:

- (void) chargeForeignCurrency: (double) foreignCurrency;

Finally, you've come to the mind-numbing part — createBudget::.

- (void) createBudget: (double) aBudget
           withExchangeRate: (float) anExchangeRate;

Note

If a method takes an argument, it has one or more colons, corresponding to the number of arguments. If it takes no arguments, it has no colons. If you aren't going to specify the full name, you add the number of colons corresponding to the number of arguments to the name. For example, createBudget:: indicates it takes two arguments.

The createBudget:: method initializes the values — the budget and exchangeRate — for an object that is the budget for a particular country. You might have coded this by assigning those values to the members in a struct data structure, as follows:

vacationBudgetEurope.exchangeRate = 1.2500;
vacationBudgetEurope.budget = 1000.00;
...
vacationBudgetEngland.exchangeRate = 1.5000;
vacationBudgetEngland.budget = 2000.00;

But because (as I explain in the earlier section "Scoping instance variables") you don't have access to the instance variables in a Budget object (say "encapsulation" three times and click your heels), you need to create a method to assign initial values to the instance variables. (Initialization is an important part of Objective-C, and I explain it in detail in Chapter 3 of this minibook.)

Although you might be able to guess that the method takes two arguments, the syntax of the declaration is probably not something you're familiar with. (Talk about a classic understatement.) Here goes:

- (void) createBudget: (double) aBudget
             withExchangeRate: (float) anExchangeRate;

When there's more than one argument, the aBudget and anExchangeRate argument names are declared within the method name after the colon. The full method name is createBudget:withExchangeRate:.

Tip

Argument names make it easier to understand the messages in your code. createBudget:withExchangeRate: does have a nice ring to it. When you create your own methods, name them in the same way — make them closer to sentences. This way of naming methods makes it much easier to match arguments with what they're used for.

This does take some getting used to, but when you do, you'll like it a lot.

Because createBudget:: won't be returning anything, I used void to indicate that there's no return value.

The @end compiler directive

The @end compiler directive does exactly what its name implies it's going to do: It tells the compiler that you've finished the interface declaration.

The complete interface for the Budget class is done. Now, anyone using this object knows that this class has three methods that can create a new budget, spend dollars, and charge something in a foreign currency: methods appropriately named createBudget:, spendDollars:, and chargeForeignCurrency:.

The Implementation — Coding the Methods

The @interface, described in the previous section, defines a class's public interface. This is the place where another developer (or even you) can go to understand the class's capabilities and behavior.

However, it's here in the implementation that the real work is described and done. Here are the steps:

  1. Add the implementation compiler directive.

  2. Define the methods.

  3. Enter the @end compiler directive.

Sounds easy enough, right? The next sections give the play-by-play account.

The @implementation compiler directive

The implementation compiler directive — made instantly recognizable by its use of @implementation — tells the compiler that you're about to present the code that implements a class. For example,

@implementation Budget

lets the compiler know that Budget is coming down the pike. The name of the class appears after @implementation.

Note

Here you code the definitions of the individual methods. When it comes to the implementation code, order is unimportant — the methods don't have to appear in the same order as they do in the @interface.

Defining the methods

After getting @implementation Budget in place, you would move on to define a method:

- (void) createBudget: (double) aBudget
            withExchangeRate: (float) anExchangeRate {
  exchangeRate = anExchangeRate;
  budget = aBudget;
}

The first line of the definition of createBudget:: looks a lot like the declaration in the @interface section (one would hope), except that instead of a semicolon at the end, you find a brace. Notice that you have an argument named aBudget and an instance variable budget. If you had named that argument budget, the compiler would have needed to decide which one you meant when you tried to access the budget variable. You want to use a name like aBudget in the method declaration because it tells the reader exactly what the argument is for.

The body of the method contains these instructions:

exchangeRate = anExchangeRate;
budget = aBudget;

Adding the following lines of code after the createBudget:: method would define two more methods:

- (void) spendDollars: (double) dollars {

  budget -= dollars;
  NSLog(@"Converting %.2f US dollars into foreign currency
                          leaves $%.2f", dollars, budget);
}

- (void) chargeForeignCurrency: (double)
                                     foreignCurrency {

  exchangeTransaction = foreignCurrency*exchangeRate;
  budget -= exchangeTransaction;
  NSLog(@"Charging %.2f in foreign currency leaves $%.2f",
                                 foreignCurrency, budget);
}

The @end compiler directive

Once again, the last line of code — the @end compiler directive — tells the compiler that you have finished the task at hand — in this case, the implementation file.

Allocating Objects

It's all well and good to declare a new class with instance variables and methods, and to write the code for these methods (as you do in the previous sections). The next step is to give birth to an object based on the new class, which happens at runtime.

Note

At runtime, a class object for each class is automatically created — one that knows how to build new objects belonging to the class. The class object is the compiled version of the class; the objects it builds are instances of the class. These instances are the objects that do the main work of your program.

Think of the class definition as really a prototype for a kind of object, not the object itself. The class definition declares the instance variables and defines a set of methods that all objects in the class can use. The objects you give birth to are instances of the class. To give birth to an object in Objective-C, you must do the following:

  1. Dynamically allocate memory for the new object.

  2. Initialize the newly allocated memory, as described in the upcoming "Initialization" section.

An object isn't fully functional until both steps have been completed. Each step is accomplished by a separate method but is typically carried out using a single line of code:

id anObject = [[Rectangle alloc] init];

Read on to find out what one simple line of code can do for you.

All objects are dynamic

Allocation (alloc) starts the process of creating a new object by getting the amount of memory it needs from the operating system to hold all of the object's instance variables. For example, in the RoadTrip app, the Trip model object is allocated (as shown in Chapter 4 of Book V) as follows:

trip = [[Trip alloc] initWithName:@"Road Trip"];

NSObject, which I explain in the section, "The @interface compiler directive and first line," earlier in this chapter, is a root class that defines the basic framework for all the Objective-C objects you use and their interactions. The alloc method is one of the principal methods of NSObject for allocating memory. This code sends the alloc message to the NSObject class. The alloc method in NSObject not only allocates the memory for the object, but also initializes all the memory it allocates to 0 — all the ints are 0; all the floats become 0.0; all the pointers are nil; and the object's isa instance variable points to the object's class. (This tells an object of what class it is an instance.)

The alloc method allocates enough memory to hold all the instance variables for an object belonging to the receiving class.

You can use the new method to combine alloc and init and instantiate an object using a line of code such as

Budget  *europeBudget = [Budget new];

This instantiates (creates) a new object and sends it a message. The new method is actually a holdover from Objective-C's earlier days when a two-phase initialization was not important. Separating allocation from initialization, as you do with alloc and init, gives you individual control over each step so that each can be modified independently of the other, but you can use the new method if you don't need to exert such control.

To create a new object, you send the new message to the class you're interested in. Here's the syntax of sending a message:

[receiver message : arguments];

The receiver of a message can be either an object or a class. One of the more interesting features of Objective-C is that you can send messages to a class. If you haven't done any object-oriented programming before, sending messages to a class probably doesn't strike you as that big of a deal. But if you're coming from something like C++, it's very interesting. By sending messages to a class, you can implement behavior that is not object-specific, but applicable to an entire class.

Note

The methods defined for an object are called instance methods, and the ones defined for a class are called class methods. Although I mention class methods in this book, you don't use them. I only refer to them when it's important to distinguish them from instance methods or those (rare) situations where you really need to know about them — in Chapter 3 of this minibook, for example.

The following line,

Budget *europeBudget = [Budget new];

sends the new message to the Budget class. The new method (inherited from NSObject) does two things, in this order:

  1. Allocates memory for the object to hold its instance variables.

  2. Sends the new object an init message.

Initialization

The default init method will (more or less) initialize the instance variables to 0. Initialization, as boring as it sounds, is a very important part of working with objects. In many cases, you don't need to initialize all your instance variables. If you can live with all the instance variables initialized to 0 and nil, then there is nothing you need to do. But if your class (or your superclass) has instance variables that you need to initialize to anything other than 0 or nil, you're going to have to code some kind of initialization method. (Chapter 3 of this minibook goes into great detail about initialization and shows you how to write a proper init method for your objects.)

Objective-C, like other object-oriented programming languages, permits you to base a new class definition on a class already defined, so that the new class inherits the methods of the class it is based on. The base class is called a superclass; the new class is its subclass (I describe inheritance in detail in Chapter 2 of this book). The superclass's initialization method is always invoked before the subclass does any initialization. Your superclass is equally as respectful of its superclass and does the same thing.

When you invoke a superclass's initialization method, most of the time you get back the object you expect. Some of the framework classes such as NSString are really class clusters — a class cluster is an abstract base class (and a group of private, concrete subclasses) that hides implementation details so that their design can be modified later, without breaking your code (as long as you use the interface provided by the abstract class). When you create an object of one of these classes, its initialization method looks at the arguments you're passing and returns the object it thinks you need. If you're playing by the rules, it won't matter (as I show in Chapter 3 of this minibook).

It's possible to have more than one initializer per class. Once you have more than one initializer in a class, according to Cocoa convention, you're expected to set one up as the designated initializer. This designated initializer is usually the one that does the most initialization, and it is the one responsible for invoking the superclass's initializer. Because this initializer is the one that does the most work, again by convention, the other initializers are expected to invoke it with appropriate default values as needed. I explore this topic further in Chapter 3 of this minibook.

Sending Messages to Your Objects

To get an object to do something, you send it a message telling it to apply a method. In Objective-C, message expressions are enclosed in brackets, like this:

[receiver message]

The receiver is an object, and the message tells it what to do. In your code, the message is simply the name of a method and any arguments that are passed to it. When a message is sent, the runtime system selects the appropriate method from the receiver's repertoire and invokes it.

For example, you could use the following code after allocating the object

Budget *europeBudget = [Budget new];:

[europeBudget createBudget:
                  1000.00 withExchangeRate:1.2500];
[europeBudget spendDollars:numberDollarsInEuroland];
[europeBudget chargeForeignCurrency:numberEuros];

This code sends three messages to the europeBudget object just instantiated. Take a look at the first message:

[europeBudget  createBudget:1000.00
                           withExchangeRate:1.2500];

Using the europeBudget pointer to the object, you are sending it the createBudget:: message with 1000.00 and 1.2500 as arguments. You use this to initialize the object with a budget and an exchange rate.

After initialization, the next message sent to the europeBudget object tells it how much was spent in dollars. (It has an argument numberDollarsInEuroland just like a function would).

[europeBudget spendDollars:numberDollarsInEuroland];

And the third message reports a credit card transaction.

[europeBudget chargeForeignCurrency:numberEuros];

The question that may occur to you at this point is how did the europeBudget method code (of which there is only a single copy) get to the object's instance variables, which are sitting some place in memory?

The answer is very clever. When you send a message in Objective-C, a hidden argument called self, a pointer to the object's instance variables, is passed to the receiving object. For example, in the code

[europeBudget spendDollars:numberDollarsInEuroland];

the method passes europeBudget as its self argument. Although the code in the method chargeForeignCurrency: looks like

NSLog(@"Converting %.2f US dollars into foreign currency
                          leaves $%.2f", dollars, budget);

what the compiler is really doing is modifying this code so that it conceptually looks like this:

NSLog(@"Converting %.2f US dollars into foreign currency
   leaves $%.2f", dollars, self->budget);

The -> is the arrow operator. It's used only with pointers to objects. As you create objects, you get a new pointer for each one, and when you send a message to a particular object, the pointer associated with that object becomes the self argument.

Working with Declared Properties

If you need to have an instance variable accessible by other objects in your program, you'll need to create accessor methods for that particular instance variable. Accessor methods effectively get (using a getter method) and set (using a setter method) the values for an instance variable.

For many years, programmers had to code accessor methods themselves or buy add-on tools that would do it for them (usually advertised late at night on the Programmers Channel). The nice folks in charge of Objective-C came to our collective rescue when they released Objective-C 2.0 with its declared properties feature. Now, the compiler can write accessor methods for you, according to the direction you give it in the property declaration. It's kind of like getting the smartest kid in your class to do your homework while you hang out with your friends at the mall. As you'll soon discover, you're sure to use declared properties a lot in your application development work. (Most people just call them properties, by the way.)

Objective-C creates the getter and setter methods for you by using a @property declaration in the interface file, combined with the @synthesize declaration in the implementation file. The default names for the getter and setter methods associated with a property are whateverThePropertyNameIs for the getter (yes, the default getter method name is the same as the property's name) and setWhateverThePropertyNameIs: for the setter. (You replace what is in italics with the actual property name or identifier.) For example, the accessors that would be generated for the exchangeRate instance variable are exchangeRate as the getter and setExchangeRate: as the setter.

Adding properties

You need to do three things in your code in order for the compiler to create accessors for you:

  1. Declare an instance variable in the interface file.

  2. Add a @property declaration of that instance variable in the same interface file.

  3. Add a @synthesize statement in the implementation file so that Objective-C generates the accessors for you.

In developing a vacation budget app as I describe in the previous sections, you might want to create a Destination class with methods that do the following:

  • Create Transaction objects from the transaction amounts that will be sent from the user interface.

  • Return whatever data the user interface needs to display.

To have your Destination class do all that, you'd have to add the following code to your class's Destination.h file; I explain the bold statements in more detail.

#import <Cocoa/Cocoa.h>
@class Budget;
@interface Destination : NSObject {

  NSString* country;
  double exchangeRate;
  NSMutableArray *transactions;
  Budget* theBudget;
}

- (id) initWithCountry: (NSString*) theCountry
           andBudget: (double) budgetAmount
           withExchangeRate: (double) theExchangeRate;
- (void) spendCash: (double) aTransaction;
- (void) chargeCreditCard: (double) aTransaction;
- (double) leftToSpend;

@property (nonatomic, retain) NSString* country;
@property (readwrite) double exchangeRate;
@end

Creating accessor methods is a two-step process that begins with the @property declaration, which tells the compiler that there are in fact accessor methods that it's going to have to deal with. The @property declarations for country and exchangeRate specify how the accessor methods are to behave.

The @property declaration specifies the name and type of the property and some attributes that provide the compiler with information about exactly how you want the accessor methods to be implemented.

For example, the declaration

@property (readwrite) double exchangeRate;

declares a property named exchangeRate, which is of the double type. The property attribute (readwrite) tells the compiler that this property can be both read and updated outside the object.

Note

You also could have specified readonly, in which case, only a getter method is required in the @implementation — you don't need a setter method with a read-only property because you're not setting anything. Then, when you use @synthesize in the implementation block, only the getter method is synthesized. Moreover, if you attempt to assign a value using the accessor — "setting" a value, in other words — you get a compiler error.

Now take a look at the declaration right before the exchangeRate declaration:

@property (nonatomic, retain) NSString* country;

It declares a property named country, which is a pointer to a NSString object. Enclosed in parentheses are two attributes: nonatomic and retain.

nonatomic addresses an important technical consideration for multithreaded systems, which is beyond the scope of this book. nonatomic works fine for applications like this one.

retain directs the compiler to create an accessor method that sends a retain message to any object that is assigned to this property. As I show in Chapter 3 of this minibook, properties can have some memory management implications — every object has its own reference count, or retain count. It's your responsibility to maintain that reference count by directly or indirectly increasing the retain count when you're using an object and then decreasing it when you're finished with it.

And, oh yes, nonatomic and retain apply only to pointers to objects.

But while the @property declaration tells the compiler that there are accessor methods coming — one if by land, two if by sea — the darn things still have to be created. In the good old days, you had to code these accessor methods yourself, which in a large program was very tedious.

Fortunately, Objective-C creates these accessor methods for you whenever you include a @synthesize statement for a property. To get this to work, you'd add the following code to the Destination.m file for the Destination class:

#import "Destination.h"
#import "CashTransaction.h"
#import "CreditCardTransaction.h"
#import "Budget.h"
#import "Transaction.h"

@implementation Destination

@synthesize exchangeRate, country;

The @synthesize statement — the bold line in the code — directs the compiler to create two accessor methods, one for each @property declaration.

Note

The @property declaration only informs the compiler that there are accessors. It is the @synthesize statement that tells the compiler to create them for you. Using @synthesize results in four new methods:

exchangeRate
setExchangeRate:
country
setCountry:

Note

If you didn't use @synthesize and you declared the properties, it would be up to you to implement the methods yourself, according to the attributes in the @property statement. So, if you were to write your own accessors, you would be responsible for sending a retain message to the exchangeRate when it's assigned to the instance variables. You may have to do that anyway, under certain circumstances, which I discuss in the later section, "Properly using properties."

Accessing the properties from within the class

After you've declared the properties, you can access them from other objects or from main. (main is the mother of all functions and is the place where all Objective-C programs start their execution — the instructions contained within main are always the first ones to be executed in Objective-C programs. All Objective-C programs have one.)

First, let me show you how you can access them within the class. If you want to take advantage of the retain message being sent automatically upon assignment, you have to access the instance variable directly, so you must invoke the accessor method, like this:

[self setCountry:theCountry];

You also can use the dot notation (something that refugees from other object-oriented languages might recognize).

self.country = theCountry;

Note

When you use the setter method with a class to assign an object pointer, you don't need to send the object a retain message, like the one you had to send to the country object in the Destination's initWithCountry:: methodethod does the retain for you.

[country retain];

Releasing the object assigned to a property

Using an accessor method automatically sends a retain message. But you still have to release it when you're done — properties are not automatically released for you.

Normally you send an object a release message:

[country release];

But if you use an accessor method, you have a new option:

self.country = nil;

That's because when you assign a new value to a property, the accessor sends a release message to the previous object. As you can see, accessors are good citizens here.

In your dealloc method, however, you need to send the object a release message.

Using accessor methods to get data from objects

The file corresponding to Destination.h for the Destination class (described earlier in the "Adding properties" section) is the Destination.m file:

#import "Destination.h"
#import "CashTransaction.h"
#import "CreditCardTransaction.h"
#import "Budget.h"
#import "Transaction.h"


@implementation Destination
@synthesize exchangeRate, country;
- (id) initWithCountry: (NSString*) theCountry andBudget:
   (double) budgetAmount withExchangeRate: (double)
   theExchangeRate{
  if (self = [super init]) {
    transactions = [[NSMutableArray alloc] initWithCapacity:10];
    theBudget = [[Budget alloc]
         initWithAmount:budgetAmount forDestination:self];
    self.exchangeRate = theExchangeRate;
    [self setCountry: theCountry];

    NSLog (@"I'm off to %@", theCountry);
  }
  return self;
}

- (void) spendCash: (double)amount{

  Transaction *aTransaction = [[CashTransaction alloc]
   initWithAmount: amount forBudget: theBudget];
  [transactions addObject:aTransaction];
  [aTransaction spend];
  [aTransaction release];

}

- (void) chargeCreditCard: (double) amount{
Transaction *aTransaction = [[CreditCardTransaction alloc]
 initWithAmount: amount forBudget: theBudget];
  [transactions addObject:aTransaction];
  [aTransaction spend];
  [aTransaction release];
}

- (double) leftToSpend {

  return [theBudget returnBalance];
}

- (void) dealloc {

  [transactions release];
  [theBudget release];
  [country release];
  [super dealloc];
}

@end

The Budget init method (bolded in the preceding code) sends a message in order to get the exchangeRate:

theBudget = [[Budget alloc]
         initWithAmount:budgetAmount forDestination:self];
    self.exchangeRate = theExchangeRate;
    [self setCountry: theCountry];

An accessor method assigns the theExchangeRate argument in the initWithAmount:: method to the exchangeRate instance variable using dot notation:

self.exchangeRate = theExchangeRate;

An accessor method also assigns the theCountry argument in the initWithAmount: method to the country instance variable using an Objective-C message.

[self setCountry:theCountry];

Now that you've created these accessor methods, you can use them. First, in the Budget.m file, you use a pointer to the Destination object and include #import "Destination.h" to make the compiler happy when it sees a message to the Destination object. Here's what the Budget.m file would look like after you add the necessary code, which is in bold here, as you can see:

#import "Budget.h"
#import "Destination.h"

@implementation Budget
- (id) initWithAmount: (double) aBudget forDestination: (Destination*) aDestination {
  if (self = [super init]) {
    destination = aDestination;
    [destination retain];
    budget = aBudget;
  }
  return self;
}

- (void) spendDollars: (double) dollars {

  budget -= dollars;
}

- (void) chargeForeignCurrency: (double) foreignCurrency {
transaction = foreignCurrency*
                               [destination exchangeRate];
  budget -= transaction;
}

- (double) returnBalance {

  return budget;
}

- (void) dealloc {
  [destination release];
  [super dealloc];
}

@end

The code you added (see the preceding bolded code) uses a pointer to the Destination object as an argument:

- (id) initWithAmount: (double) aBudget forDestination: (Destination*) aDestination {
  if (self = [super init]) {
    destination = aDestination;
    [destination retain];
    budget = aBudget;
  }
  return self;
}

It also stores the pointer to the Destination object in an instance variable destination. You send it a retain message to ensure that it won't be deallocated until you are done with it.

The chargeForeignCurrency: method uses the getter method exchangeRate to get the exchange rate from the Destination object.

Next, in the Budget.h file, you add a @class statement to make the compiler happy, as well as an instance variable, destination, and an init method declaration just like in the implementation:

#import <Cocoa/Cocoa.h>
@class Destination;

@interface Budget : NSObject {
  float        exchangeRate;
  double       budget;
  double       transaction;
  Destination* destination;
}
- (id) initWithAmount: (double) aBudget
               forDestination: (Destination*) aDestination;

- (void) spendDollars: (double) dollars ;
- (void) chargeForeignCurrency: (double) euros;
- (double) returnBalance;
@end

In main, you would use the Destination object's setExchangeRate: and country accessor methods to update the exchange rate and access the country name and display it:

[europe setExchangeRate:1.30];
[england setExchangeRate:1.40];

You would also send a setExchangeRate: message to both the europe and england objects, which updates the exchange rate for each, replacing the initialized value for exchangeRate. And being a good citizen about recycling memory for other uses, you also release the string returnedCountry:

NSString *returnedCountry = [england country];
  NSLog (@"You have deleted the %@ part of your
   trip",returnedCountry);
  [returnedCountry release];
  [england release];

At the top of main, you would use the autorelease pool allocation to store objects in the autorelease pool, and at the bottom, pool drain to empty the autorelease pool as I describe in Chapter 3 of this minibook:

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

...

[pool drain];

An autorelease pool contains objects that have received an autorelease message; when drained, it sends a release message to each of those objects. By sending autorelease instead of release to an object, you can extend the lifetime of the objects — at least until the pool itself is drained.

Properly using properties

There are times when accessor methods are the best way to do things:

  • Customizing user interface objects. In a framework, the user interface object — a window or view, for example — really needs to have certain parameters set to make it function in the way the user needs. Instead of forcing the user to subclass it, properties allow it to be tailored to a particular user's (the developer's) needs. In this case, properties are being used to set parameters, like color, rather than to implement a class's responsibility to accept data.

  • Accessing instance variables. Again, in a framework, the same argument applies to accessing the instance variables. The instance variables should become properties when they hold information about the state of the object — is the window opened or closed, where did the user just drag this object to on the screen, and so on.

Except for those and similar circumstances in your own classes, you're much better off avoiding using properties to implement an object's responsibility to accept data from (and supply data to) other objects. You should define methods that accept or supply data and not use a property that implies structural information about the data.

That being said, some features about properties also allow you to do some interesting things to mitigate the impact if you later decide to change an instance variable you've made available as a property. For example:

  • To deal with changes, you can implement the accessor method (instead of having it generated by the complier) to access the property. For example, if you moved the exchange rate to an exchange rate object, you could implement your own exchangeRate method currently synthesized by the compiler. (It will only synthesize those methods if you have not implemented them in your implementation file.) The method you implemented would send a message to the new exchange rate object to get — and then return back — the exchange rate. (You probably wouldn't need a setter in this case.) If you do that though, be sure to implement the accessor in a way that's consistent with the property's attributes. Creating your own accessors for properties is another topic that is beyond the scope of this book.

  • The accessor doesn't have to be named the same as the instance variable. For example, you can use

    @property (readwrite, getter=returnTheExchangeRate)
                                      double exchangeRate;
  • The property name must have the same name as an instance variable. For example,

    @property (readwrite ) double er;
    ...
    @synthesize country, er = exchangeRate;

    directs the complier to synthesize getEr and setEr: to get and set the instance variable exchangeRate. If you try this for yourself, you find that

    [europe setEr:1.30];
    [england setEr:1.40];

    works just as well as setExchangeRate: does.

Extending the Program

As you discover earlier in this chapter, coding procedurally (as you would in C) using a data structure and set of functions for a vacation budget app to work in a particular country would require you to modify both the data structure and the set of functions for the other countries. Adding new functions for each country would be a lot of work, and changing the data structure would require changing all the functions that use it. This vulnerability you face — when all your functions have access to all the data and are dependent on that data's structure — is mostly solved by encapsulating the data in an object. The data becomes an internal implementation detail; the other objects that use this object's data know only the behavior they can expect from this object.

But what if another object needs to know the amount left in your budget for England, for example? To meet that need, you add a method that provides that information. Notice I say information, not the instance variable. It becomes the responsibility of an object to supply the budget information to any object that needs it. It doesn't mean, however, that there has to be an instance variable that holds that information. That makes it possible to change how you represent that data and also makes it possible to change what instance variables you choose for the object.

So, although its internal data structure is part of the class interface, in reality, an object's functionality should be defined only by its methods. As a user of a class, you shouldn't count on a one-to-one correspondence between a method that returns some data and an instance variable. Some methods might return information not stored in instance variables, and some instance variables might have data that will never see the light of day outside the object.

This setup allows your classes to evolve over time. As long as messages are the way you interact with a class, changes to the instance variables really don't affect its interface and also don't affect the other objects that use this class — and that's the point.

But what if you want a new kind of budget or want to tailor your Budget object to New Zealand to keep track of your wool purchases? Do you have to take the old object, copy and paste it, and add the new features — thus creating a new object that you have to maintain in parallel with the existing Budget object?

As you might expect, the answer is, "Of course not!" As you learn about an inheritance-based class structure, this greatly simplifies things, and you end up with a program that is a great deal easier to understand and extend. (The two actually go hand in hand.)

Inheritance

Objective-C, like other object-oriented programming languages, permits you to base a new class definition on a class already defined. The base class is called a superclass; the new class is its subclass. The subclass is defined only by its extension to its superclass; everything else remains the same. Each new class that you define inherits methods and instance variables of its superclass.

Inheritance allows you to do a number of things that make your programs more extensible. In a subclass, you can make three kinds of changes to the methods inherited from a superclass: add new methods and instance variables, refine or extend the behavior of a method, and change the behavior of an inherited method.

As a result, you can use a superclass's declaration (its list of methods) to define a modus operandi that all its subclasses must follow. When different classes implement similarly named methods, a program is better able to make use of polymorphism, which I describe in Chapter 2 of this minibook — adding new objects of the same type, and having your program handle them without making any changes to the code that uses them. You can also reuse code: If classes have some things in common, but also differ in key ways, the common functionality can be put in a superclass that all classes can inherit.

Note

After you get into the rhythm of thinking this way, programming and making changes becomes more fun and less dreary. You introduce fewer bugs as you add functionality to your program, and your coding becomes completely focused on the new functionality instead of having to go back through everything you've done to see whether you're about to break something that now works just fine.

Delegation

Delegation is a pattern used extensively in the UIKit and AppKit frameworks to customize the behavior of an object without subclassing. Instead, one object (a framework object) delegates the task of implementing one of its responsibilities to another object. You're using a behavior-rich object supplied by the framework as is and putting the code for program-specific behavior in a separate (delegate) object. When a request is made of the framework object, the method of the delegate that implements the program-specific behavior is automatically called.

You're basically using delegation to get other objects to do the work for you, as I describe in Chapter 5 of this minibook. But first, you need to discover the real truth about object-oriented programming, find out how objects behave, and especially get to know all about encapsulation, polymorphism, and how inheritance really works — all that and more is in Chapter 2.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.223.169