You’re reading Ry’s Objective-C Tutorial |
Methods
Methods represent the actions that an object knows how to perform. They’re the logical counterpart to properties, which represent an object’s data. You can think of methods as functions that are attached to an object; however, they have a very different syntax.
In this module, we’ll explore Objective-C’s method naming conventions, which can be frustrating for experienced developers coming from C++, Java, Python, and similar languages. We’ll also briefly discuss Objective-C’s access modifiers (or lack thereof), and we’ll learn how to refer to methods using selectors.
Naming Conventions
Objective-C methods are designed to remove all ambiguities from an API. As a result, method names are horribly verbose, but undeniably descriptive. Accomplishing this boils down to three simple rules for naming Objective-C methods:
- Don’t abbreviate anything.
- Explicitly state parameter names in the method itself.
- Explicitly describe the return value of the method.
Keep these rules in mind as you read through the following exemplary
interface for a Car
class.
// Car.h
#import
<Foundation/Foundation.h>
@interface
Car
:NSObject
// Accessors
-
(
BOOL
)
isRunning
;
-
(
void
)
setRunning:
(
BOOL
)
running
;
-
(
NSString
*
)
model
;
-
(
void
)
setModel:
(
NSString
*
)
model
;
// Calculated values
-
(
double
)
maximumSpeed
;
-
(
double
)
maximumSpeedUsingLocale:
(
NSLocale
*
)
locale
;
// Action methods
-
(
void
)
startEngine
;
-
(
void
)
driveForDistance:
(
double
)
theDistance
;
-
(
void
)
driveFromOrigin:
(
id
)
theOrigin
toDestination:
(
id
)
theDestination
;
-
(
void
)
turnByAngle:
(
double
)
theAngle
;
-
(
void
)
turnToAngle:
(
double
)
theAngle
;
// Error handling methods
-
(
BOOL
)
loadPassenger:
(
id
)
aPassenger
error:
(
NSError
**
)
error
;
// Constructor methods
-
(
id
)
initWithModel:
(
NSString
*
)
aModel
;
-
(
id
)
initWithModel:
(
NSString
*
)
aModel
mileage:
(
double
)
theMileage
;
// Comparison methods
-
(
BOOL
)
isEqualToCar:
(
Car
*
)
anotherCar
;
-
(
Car
*
)
fasterCar:
(
Car
*
)
anotherCar
;
-
(
Car
*
)
slowerCar:
(
Car
*
)
anotherCar
;
// Factory methods
+
(
Car
*
)
car
;
+
(
Car
*
)
carWithModel:
(
NSString
*
)
aModel
;
+
(
Car
*
)
carWithModel:
(
NSString
*
)
aModel
mileage:
(
double
)
theMileage
;
// Singleton methods
+
(
Car
*
)
sharedCar
;
@end
Abbreviations
The easiest way to make methods understandable and predictable is to simply
avoid abbreviations. Most Objective-C programmer expect methods to be
written out in full, as this is the convention for all of the standard
frameworks, from Foundation
to UIKit.
This is why the above interface chose maximumSpeed
over the more
concise maxSpeed
.
Parameters
One of the clearest examples of Objective-C’s verbose design philosophy is in the naming conventions for method parameters. Whereas C++, Java, and other Simula-style languages treat a method as a separate entity from its parameters, an Objective-C method name actually contains the names of all its parameters.
For example, to make a Car
turn by 90 degrees in C++, you would
call something like turn(90)
. But, Objective-C finds this too
ambiguous. It’s not clear what kind of argument turn()
should take—it could be the new orientation, or it could be an angle by
which to increment your current orientation. Objective-C methods make this
explicit by describing the argument with a preposition. The resulting API
ensures the method will never be misinterpreted: it’s either
turnByAngle:90
or turnToAngle:90
.
When a method accepts more than one parameter, the name of each argument is
also included in the method name. For instance, the above
initWithModel:mileage:
method explicitly labels both the
Model
argument and the mileage
argument. As
we’ll see in a moment, this makes for very informative method
invocations.
Return Values
You’ll also notice that any methods returning a value explicitly state what that value is. Sometimes this is as simple as stating the class of the return type, but other times you’ll need to prefix it with a descriptive adjective.
For example, the factory methods start with car
, which clearly
states that the method will return an instance of the Car
class.
The comparison methods fasterCar:
and slowerCar:
return the faster/slower of the receiver and the argument, and this is also
clearly expressed in the API. It’s worth noting that singleton methods
should also follow this pattern (e.g., sharedCar
), since the
conventional instance
method name is ambiguous.
For more information about naming conventions, please visit the official Cocoa Coding Guidlines.
Calling Methods
As discussed in the Instantiation and Usage section, you invoke a method by placing the object and the desired method in square brackets, separated by a space. Arguments are separated from the method name using a colon:
[
porsche
initWithModel:
@"Porsche"
];
When you have more than one parameter, it comes after the initial argument, following the same pattern. Each parameter is paired with a label, separated from other arguments by a space, and set off by a colon:
[
porsche
initWithModel:
@"Porsche"
mileage:
42000.0
];
It’s a lot easier to see the purpose behind the above naming conventions when you approach it from an invocation perspective. They make method calls read more like a human language than a computer one. For example, compare the following method call from Simula-style languages to Objective-C’s version:
// Python/Java/C++
porsche
.
drive
(
"Home"
,
"Airport"
);
// Objective-C
[
porsche
driveFromOrigin:
@"Home"
toDestination:
@"Airport"
];
It might be more to type, but that’s why Xcode comes with such a nice auto-completion feature. You’ll appreciate the verbosity when you leave your code for a few months and come back to fix a bug. This clarity also makes it much easier to work with third-party libraries and to maintain large code bases.
Nested Method Calls
Nesting method calls is a common pattern in Objective-C programs. It’s a natural way to pass the result of one call to another. Conceptually, it’s the exact same as chaining methods, but the square-bracket syntax makes them look a little bit different:
// JavaScript
Car
.
alloc
().
init
()
// Objective-C
[[
Car
alloc
]
init
];
First, the [Car alloc]
method is invoked, then the
init
method is called on its return value.
Protected and Private Methods
There are no protected or private access modifiers for Objective-C methods—they are all public. However, Objective-C does provide alternative organizational paradigms that let you emulate these features.
“Private” methods can be created by defining them in a class’s implementation file while omitting them from its interface file. Since other objects (including subclasses) are never supposed to import the implementation, these methods are effectively hidden from everything but the class itself.
In lieu of protected methods, Objective-C provides categories, which are a more general solution for isolating portions of an API. A full example can be found in “Protected” Methods.
Selectors
Selectors are Objective-C’s internal representation of a method name. They let you treat a method as an independent entity, enabling you to separate an action from the object that needs to perform it. This is the basis of the target-action design pattern, which is introduced in the Interface Builder chapter of Ry’s Cocoa Tutorial. It’s also an integral part of Objective-C’s dynamic typing system.
There are two ways to get the selector for a method name. The
@selector()
directive lets you convert a source-code method name
to a selector, and the NSSelectorFromString()
function lets you
convert a string to a selector (the latter is not as efficient). Both of these
return a special data type for selectors called SEL
. You can use
SEL
the exact same way as BOOL
, int
, or
any other data type.
// main.m
#import
<Foundation/Foundation.h>
#import
"Car.h"
int
main
(
int
argc
,
const
char
*
argv
[])
{
@autoreleasepool
{
Car
*
porsche
=
[[
Car
alloc
]
init
];
porsche
.
model
=
@"Porsche 911 Carrera"
;
SEL
stepOne
=
NSSelectorFromString
(
@"startEngine"
);
SEL
stepTwo
=
@selector
(
driveForDistance:
);
SEL
stepThree
=
@selector
(
turnByAngle:quickly:
);
// This is the same as:
// [porsche startEngine];
[
porsche
performSelector:
stepOne
];
// This is the same as:
// [porsche driveForDistance:[NSNumber numberWithDouble:5.7]];
[
porsche
performSelector:
stepTwo
withObject:
[
NSNumber
numberWithDouble:
5.7
]];
if
([
porsche
respondsToSelector:
stepThree
])
{
// This is the same as:
// [porsche turnByAngle:[NSNumber numberWithDouble:90.0]
// quickly:[NSNumber numberWithBool:YES]];
[
porsche
performSelector:
stepThree
withObject:
[
NSNumber
numberWithDouble:
90.0
]
withObject:
[
NSNumber
numberWithBool:
YES
]];
}
NSLog
(
@"Step one: %@"
,
NSStringFromSelector
(
stepOne
))
;
}
return
0
;
}
Selectors can be executed on an arbitrary object via
performSelector:
and related methods. The withObject:
versions let you pass an argument (or two) to the method, but require those
arguments to be objects. If this is too limiting for your needs, please see NSInvocation
for advanced usage. When you’re not sure if the target object defines the
method, you should use the respondsToSelector:
check before trying
to perform the selector.
The technical name for a method is the primary method name concatenated with all of its parameter labels, separated by colons. This makes colons an integral aspect of method names, which can be confusing to Objective-C beginners. Their usage can be summed up as follows: parameterless methods never contain a colon, while methods that take a parameter always end in a colon.
A sample interface and implementation for the above Car
class
is included below. Notice how we have to use NSNumber
instead of
double
for the parameter types, since the
performSelector:withObject:
method doesn’t let you pass
primitive C data types.
// Car.h
#import
<Foundation/Foundation.h>
@interface
Car
:NSObject
@property
(
copy
)
NSString
*
model
;
-
(
void
)
startEngine
;
-
(
void
)
driveForDistance:
(
NSNumber
*
)
theDistance
;
-
(
void
)
turnByAngle:
(
NSNumber
*
)
theAngle
quickly:
(
NSNumber
*
)
useParkingBrake
;
@end
// Car.m
#import
"Car.h"
@implementation
Car
@synthesize
model
=
_model
;
-
(
void
)
startEngine
{
NSLog
(
@"Starting the %@'s engine"
,
_model
);
}
-
(
void
)
driveForDistance:
(
NSNumber
*
)
theDistance
{
NSLog
(
@"The %@ just drove %0.1f miles"
,
_model
,
[
theDistance
doubleValue
]);
}
-
(
void
)
turnByAngle:
(
NSNumber
*
)
theAngle
quickly:
(
NSNumber
*
)
useParkingBrake
{
if
([
useParkingBrake
boolValue
])
{
NSLog
(
@"The %@ is drifting around the corner!"
,
_model
);
}
else
{
NSLog
(
@"The %@ is making a gentle %0.1f degree turn"
,
_model
,
[
theAngle
doubleValue
]);
}
}
@end
Summary
This module explained the reasoning behind Objective-C’s method naming
conventions. We also learned that there are no access modifiers for Objective-C
methods, and how to use @selector
to dynamically invoke
methods.
Adapting to new conventions can be a frustrating process, and the dramatic syntactic differences between Objective-C and other OOP languages won’t make your life any easier. Instead of forcing Objective-C into your existing mental model of the programming universe, it helps to approach it in its own right. Try designing a few simple programs before passing judgement on Objective-C’s verbose philosophy.
That covers the basics of object-oriented programming in Objective-C. The rest of this tutorial explores more advanced ways to organize your code. First on the list are protocols, which let you share an API between several classes.
Be sure to check out Ry’s Cocoa Tutorial. This brand new guide is a complete walkthrough of Mac App development, and it leverages all of the Objective-C skills that we just discussed. Learn more › |
Mailing List
Sign up for my low-volume mailing list to find out when new content is released. Next up is a comprehensive Swift tutorial planned for late January.
You’ll only receive emails when new tutorials are released, and your contact information will never be shared with third parties. Click here to unsubscribe.