What You’ll Learn in This Hour
Examining the difference between class and instance methods
Naming methods properly
Returning complex data structures as method results
As you have seen in the previous hours of this part, you declare classes that can contain instance variables and declared properties in addition to the information in the class declaration itself. The other major component of a class is its methods. Methods are the messages to which instances of the class (or, in some cases, the class itself) respond. They provide the functionality for the class.
In this hour, you learn how to declare and define your classes. To help you create useful methods, you also see the basics of how people can use the classes that you create.
Objective-C methods use much the same syntax as do C functions. The major difference is that Objective-C methods use named arguments. Inside the body of a method, the structure is the same as the inside of a C function, although, of course, you can use Objective-C syntax in addition to the standard C syntax. Remember that C code compiles properly under Objective-C, so nothing prevents you from mixing Objective-C and C code; you also can write a method that consists solely of C code.
Because Objective-C methods are so similar to C functions, a good place to start is with the naming conventions for Objective-C and Cocoa methods. The naming conventions provide standards that make your code easier to read (this is valuable even on a one-person project). In addition, the options and standards help you to appreciate the features and options that are available to you as you develop Objective-C methods.
As you have seen, the basic syntax for a method declaration consists of two basic parts followed, as always with a declaration, by a semicolon. The two parts are the instance/class indicator and the method header itself.
The first character indicates if this is a class instance (+) or a method instance (-). Class instances are called using the class name rather than a specific instance. They often are used as factory methods to create new instances of the class. In many cases, these new instances represent a conversion from other classes (or even basic types) to the class.
For example, NSNumber
has class methods that return new instances of NSNumber
based on values that are passed in. Here is a commonly used method:
+ (NSNumber *)numberWithBool:(BOOL)value;
You pass in a BOOL
and receive a new instance of NSNumber
. You perform this method from an implementation file by using the following:
NSNumber *myNumber = [NSNumber numberWithBool: YES];
A more common invocation would use a variable for the argument:
BOOL myBool = YES;
NSNumber *myNumber = [NSNumber numberWithBool: myBool];
An instance method is declared with a - at the beginning. Most init
methods initialize an already-created instance and set the appropriate values, as follows:
– (id)initWithBool:(BOOL)value;
The difference between the instance method and the class method is that you must have created the instance first. Thus, you need both of these lines of code:
myNumber = [NSNumber alloc];
[myNumber initWithBool: myBool];
As shown previously, you can combine these two lines of code into one:
myNumber = [[NSNumber alloc] initWithBool: myBool];
Tip: Copying and Pasting Method Declarations and Implementations
If you have a method declaration such as
– (id)initWithBool:(BOOL)value;
you can copy and paste it into your implementation. Delete the final semicolon and type a single open brace. When you press Return, Xcode adds a blank line and a closing brace. You can now start to write the code.
– (id)initWithBool:(BOOL)value {
}
Look closely at a method declaration to understand its three basic components. This is a method declaration that has been used previously in this hour and one that is frequently used by programmers:
– (id)numberWithBool:(BOOL)value;
After the instance/class indicator, the components are the following:
Return value
—Just as with C functions, each method can return a result (in fact, all methods return a result, as you see later in this section). The result is a single value, but it can be a collection object such as NSDictionary
or NSArray
, which can contain multiple values within the single object.
Method name
—This is a standard Objective-C identifier. Naming standards for methods are described in this section.
Arguments
—There can be zero or more of these. As with C functions, you can end the list with an ellipsis (...) to indicate a variable number of arguments (this is called a variadic method or function). That style is rarely used today in Objective-C, and it is not discussed in this book.
Every method returns the same type of value: an id
. This is a pointer to an object—any object. (Note that you don’t use an asterisk with the id
type.) Standard practice is to cast the result of a method to a more specific value using the standard casting syntax. For example, consider this class method:
+ (NSNumber *)numberWithBool:(BOOL)value;
The returned id
(a pointer to an object) is cast to being a pointer to an NSNumber
instance. This enables better compile-time checking of the code that you write.
Perhaps the most common casting of a method result is not to a pointer to an object; it is cast to void
. You can see this in methods such as the following:
– (void)setIdentifier:(NSString *)identifier
For methods that return values that are not objects, you can use the standard C types such as float
, short
, double
, and unsigned char
.
By convention, a method name begins with a lowercase character. If it consists of several words, combine them by starting each word after the first one with a capital letter. Do not use underscores or other special characters.
Method names are often verbs. You may modify them, but somewhere (preferably at the beginning), there should be a verb. You have already seen examples of simple methods that consist only of a verb (init
and alloc
, for example).
Tip
Although “do” is a verb, for the sake of method naming, ignore it. doDuplicate
is not a good method name: Simply use duplicate
. do
adds nothing to the method name.
If a method performs several tasks, they should normally be reflected in the method name unless they are clearly part of the main task. As the frequently cited example of alloc
and init
shows, it can be better to have two separate methods that cooperate by returning a value that is passed into the next one. That way, you know what is happening.
Tip: Do Not Hide Method Tasks
The only exception to this guideline is the case in which one or more tasks are subsidiary and are so essential that it is inconceivable that the method’s main task could be performed without them. This guidance is particularly important for tasks that create, modify, or change data. If your method is going to create a certain object with a name that is passed into the method, checking to see if the name is a duplicate is pretty clearly part of the basic processing, and there often is no reason to separate the process into two tasks.
What is common, however, is to create a composite task that interacts with the user. You might have a task called createNamedObject
that returns an NSError
object. The composite task createNamedObjectWithError
could add user interaction for error handling. Both methods would ultimately do the same thing, but this structure enables you to create a pair of methods that can be used in a variety of circumstances.
Many methods do not perform tasks, and in these cases, verbs are inappropriate. If a method returns a value, the beginning of its method name should be the name of what it is returning; get
is as superfluous as do
.
Methods can accept any number of arguments. A colon precedes each argument. When you are looking at a list of methods (perhaps in a class reference), this fact is important. For example, Listing 10.1 shows the instance methods for working with state information in NSPersistentStore
.
– type
– persistentStoreCoordinator
– configurationName
– options
– URL
– setURL:
– identifier
– setIdentifier:
– isReadOnly
– setReadOnly:
The colons let you distinguish between the methods that take no arguments and the three methods listed below that take a single argument:
setURL
setIdentifier
setReadOnly
Methods can take more than one argument. Here is a method from NSPersistentStore
that takes three arguments:
+ setMetadata:forPersistentStoreWithURL:error:
As you can see, each argument follows a colon. In all cases except the first argument, the argument has a name. In the case of the first argument, the name of the argument is the name of the method.
Particularly when a method has several arguments, the method declaration becomes a meaningful phrase and sometimes even a sentence. The method name is frequently a verb, and the argument names often include a preposition as in the example just shown. A good example of the argument is forPersistentStoreWithURL
. That particular argument has a bonus by having two prepositions. forPersistentStore
and WithURL
provide clear indications of how setMetadata
functions.
Also, the final argument, error
, illustrates another common aspect of argument naming. Certain arguments such as error
are so common that it is not necessary to decorate them with a preposition. You could name the final argument withError
, but the convention of a final argument named error
is sufficient for most people.
You can easily return a result from a method. The default return value is id
, and you can cast it to a pointer to a class instance such as NSView *
or to the common void
, which effectively returns nothing from the method.
Because the result is a pointer to a class instance in many cases, it can carry all of the data in that class instance. Many (possibly most) classes have significant amounts of data. The NSNumber
class is used in this hour precisely because it does not have much data, and you can see how its methods work.
Some classes are specifically designed to manage large amounts of data.
GO TO Hour 15, “Organizing Data with Collections,” p. 205, to learn how to work with dictionaries, sets, and arrays.
Pointers can be dereferenced with &
so as to get to the underlying data. This is commonly used with NSError
. Some methods use double indirection in their declaration, as shown here for outError
:
– (BOOL)readFromURL:(NSURL *)url error:(NSError **)outError
You can invoke it with code such as the following:
NSError *err = nil;
myResult = [myDocument readFromURL:myURL error: &error];
The argument myURL
is a pointer to the NSURL
class. outError
is a pointer to a pointer to the NSError
class (this is called double indirection). If the method makes a change to myURL
, it affects the object passed in via the argument. With the double indirection of outError
, the method can change the pointer so that it points to another object. Typically, it is used to change it from its initial value (nil) to another instance of NSError
. In this way, a method can return a new value not only as its result (BOOL
, in this case), but also through a doubly indirected argument.
The biggest difference between returning an object as a method result and returning an object using double indirection as described here is that you can only return one method result (although it can contain many elements within the object returned), but with double indirection you can have several arguments that can return results. Before embarking on this architecture, think about whether this is the best structure.
In many cases, if you think you need to return three separate results through arguments, maybe you need to construct a new class or even a container class
such as NSDictionary
that can bring those separate results into a single, logical form. Returning several values that are related only by a single method call may indicate that the method is too general or is not focused enough.
The common use of an NSError
argument that is passed in as nil and may be returned as a value or still as nil is a good design because in most cases, nothing new is returned. If there is no error, nil is passed in and nil is returned. Returning a new NSError
object (or any other object in other cases) is appropriate when the primary result is returned as the method’s result and the other result is returned either in an error condition or another rare and exceptional circumstance.
This all boils down to a matter of programming style and personal preference, but most developers have come to believe that the simplest implementation of any process is best if only because when new developers come along to maintain it, they will understand what it does.
This hour has shown you how to declare methods in your interface. Methods are of two types: instance and class methods. Class methods are called on the class itself—often they are factory methods that create new instances of the class. Each type of method then has three primary components: a result, a name, and zero or more arguments.
Q. What is the purpose of a doubly indirected argument in a method declaration?
A. With a typical argument (single indirection such as NSError *
), you pass in a pointer to an instance; you may modify it as a result of the method’s code. (This is common in the init
methods.) With double indirection such as NSError **
, you typically pass in a nil value and return a new object. (In some cases, you pass in one object and return another.)
Q. Why is the ellipsis not used much at the end of a list of method arguments?
A. The code can be clearer if each argument is named. If you need a flexible number of arguments, consider using one of the collection classes such as NSArray
, NSSet
, or NSDictionary
.
1. What does (void
) mean at the beginning of a method declaration?
2. What are the basic rules of method declaration naming?
1. Nothing is returned.
2. The method itself should be a verb if it does something; if it simply returns a value, it should be the name of the value. Argument names typically end with a prepositional phrase such as withStyle
.
How many problems can you find with these declarations?
– convertRectToCircle: (NSRect *)aRect;
– doMainProcess;
– (MyUserClass *)getUserIDForName: (NSString*)aName;
18.118.137.67