In this chapter, we'll explore Objective-C methods in much more detail than we have in previous chapters. This includes an in-depth discussion of instance methods, class methods, important built-in methods, inheritance, naming conventions, and common design patterns.
Instance vs. Class Methods
We've been working with both instance and class methods throughout this book, but let's take a moment to formalize the two major categories of methods in Objective-C:
- Instance methods - Functions bound to an object. Instance methods are the "verbs" associated with an object.
- Class methods - Functions bound to the class itself. They cannot be used by instances of the class. These are similar to static methods in C#.
As we've seen many times, instance methods are denoted by a hyphen before the method name, whereas class methods are prefixed with a plus sign. For example, let's take a simplified version of our Person.h
file:
@interface Person : NSObject @property (copy) NSString *name; - (void)sayHello; + (Person *)personWithName:(NSString *)name; @end
Likewise, the corresponding implementation methods also need to be preceded by a hyphen or a plus sign. So, a minimal Person.m
might look something like:
#import "Person.h" @implementation Person @synthesize name = _name; - (void)sayHello { NSLog(@"HELLO"); } + (Person *)personWithName:(NSString *)name { Person *person = [[Person alloc] init]; person.name = name; return person; } @end
The sayHello
method can be called by instances of the Person
class, whereas the personWithName
method can only be called by the class itself:
Person *p1 = [Person personWithName:@"Frank"]; // Class method. [p1 sayHello]; // Instance method.
Most of this should be familiar to you by now, but now we have the opportunity to talk about some of the unique conventions in Objective-C.
The Super Keyword
In any object-oriented environment, it's important to be able to access methods from the parent class. Objective-C uses a very similar scheme to C#, except instead of base
, it uses the super
keyword. For example, the following implementation of sayHello
would display HELLO
in the output panel, and then call the parent class' version of sayHello
:
- (void)sayHello { NSLog(@"HELLO"); [super sayHello]; }
Unlike in C#, override methods do not need to be explicitly marked as such. You'll see this with both the init
and dealloc
methods discussed in the following section. Even though these are defined on the NSObject
class, the compiler doesn't complain when you create your own init
and dealloc
methods in subclasses.
Initialization Methods
Initialization methods are required for all objects-a newly allocated object is not considered "ready to use" until one of its initialization methods has been called. They are the place to set defaults for instance variables and otherwise set up the state of the object. The NSObject
class defines a default init
method that doesn't do anything, but it's often useful to create your own. For example, a custom init
implementation for our Ship
class could assign default values to an instance variable called _ammo
:
- (id)init { self = [super init]; if (self) { _ammo = 1000; } return self; }
This is the canonical way to define a custom init
method. The self
keyword is the equivalent of C#'s this
-it's used to refer to the instance calling the method, which makes it possible for an object to send messages to itself. As you can see, all init
methods are required to return the instance. This is what makes it possible to use the [[Ship alloc] init]
syntax to assign the instance to a variable. Also notice that because the NSObject
interface declares the init
method, there is no need to add an init
declaration to Ship.h
.
While simple init
methods like the one shown in the previous sample are useful for setting default instance variable values, it's often more convenient to pass parameters to an initialization method:
- (id)initWithAmmo:(unsigned int)theAmmo { self = [super init]; if (self) { _ammo = theAmmo; } return self; }
If you're coming from a C# background, you might be uncomfortable with the initWithAmmo
method name. You'd probably expect to see the Ammo
parameter separated from the actual method name like void init(uint ammo)
; however, Objective-C method naming is based on an entirely different philosophy.
Recall that Objective-C's goal is to force an API to be as descriptive as possible, ensuring that there is absolutely no confusion as to what a method call is going to do. You can't think of a method as a separate entity from its parameters-they are a single unit. This design decision is actually reflected in Objective-C's implementation, which makes no distinction between a method and its parameters. Internally, a method name is actually the concatenated parameter list.
For example, consider the following three method declarations. Note that the second and third are not built-in methods of NSObject
, so you do need to add them to the class' interface before implementing them.
- (id)init; - (id)initWithAmmo:(unsigned int)theAmmo; - (id)initWithAmmo:(unsigned int)theAmmo captain:(Person *)theCaptain;
While this looks like method overloading, it's technically not. These are not variations on the init
method-they are all completely independent methods with distinct method names. The names of these methods are as follows:
init initWithAmmo: initWithAmmo:captain:
This is the reason you see notation like indexOfObjectWithOptions:passingTest:
and indexOfObjectAtIndexes:options:passingTest:
for referring to methods in the official Objective-C documentation (taken from NSArray).
From a practical standpoint, this means that the first parameter of your methods should always be described by the "primary" method name. Ambiguous methods like the following are generally frowned upon by Objective-C programmers:
- (id)shoot:(Ship *)aShip;
Instead, you should use a preposition to include the first parameter in the method name, like so:
- (id)shootOtherShip:(Ship *)aShip;
Including both OtherShip
and aShip
in the method definition may seem redundant, but remember that the aShip
argument is only used internally. Someone calling the method is going to write something like shootOtherShip:discoveryOne
, where discoveryOne
is the variable containing the ship you want to shoot. This is exactly the kind of verbosity that Objective-C developers strive for.
Class Initialization
In addition to the init
method for initializing instances, Objective-C also provides a way to set up classes. Before calling any class methods or instantiating any objects, the Objective-C runtime calls the initialize
class method of the class in question. This gives you the opportunity to define any static variables before anyone uses the class. One of the most common use cases for this is to set up singletons:
static Ship *_sharedShip; + (void)initialize { if (self == [Ship class]) { _sharedShip = [[self alloc] init]; } } + (Ship *)sharedShip { return _sharedShip; }
Before the first time [Ship sharedShip]
is called, the runtime will call [Ship initialize]
, which makes sure the singleton is defined. The static variable modifier serves the same purpose as it does in C#-it creates a class-level variable instead of an instance variable. The initialize
method is only called once, but it's called on all super classes, so you have to take care not to initialize class-level variables multiple times. This is why we included the self == [Ship class]
conditional to make sure _shareShip
is only allocated in the Ship
class.
Also note that inside of a class method, the self
keyword refers to the class itself, not an instance. So, [self alloc]
in the last example is the equivalent of [Ship alloc]
.
Deallocation Methods
The logical counterpart to an instance's initialization method is the dealloc
method. This method is called on an object when its reference count reaches zero and its underlying memory is about to be deallocated.
Deallocation in MMR
If you're using manual memory management (not recommended), you need to release any instance variables that your object allocated in the dealloc
method. If you don't free instance variables before your object goes out of scope, you'll have dangling pointers to your instance variables, which means leaked memory whenever an instance of the class is released. For example, if our Ship
class allocated a variable called _gun
in its init
method, you would have to release it in dealloc
. This is demonstrated in the following example (Gun.h
contains an empty interface that simply defines the Gun
class):
#import "Ship.h" #import "Gun.h" @implementation Ship { BOOL _gunIsReady; Gun *_gun; } - (id)init { self = [super init]; if (self) { _gun = [[Gun alloc] init]; } return self; } - (void)dealloc { NSLog(@"Deallocating a Ship"); [_gun release]; [super dealloc]; } @end
You can see the dealloc
method in action by creating a Ship
and releasing it, like so:
int main(int argc, const char * argv[]) { @autoreleasepool { Ship *ship = [[Ship alloc] init]; [ship autorelease]; NSLog(@"Ship should still exist in autoreleasepool"); } NSLog(@"Ship should be deallocated by now"); return 0; }
This also demonstrates how auto-released objects work. The dealloc
method won't be called until the end of the @autoreleasepool
block, so the previous code should output the following:
Ship should still exist in autoreleasepool Deallocating a Ship Ship should be deallocated by now
Note that the first NSLog()
message in main()
is displayed before the one in the dealloc
method, even though it was called after the autorelease
call.
Deallocation in ARC
However, if you're using automatic reference counting, all of your instance variables will be deallocated automatically, and [super dealloc]
will be called for you as well (you should never call it explicitly). So, the only thing you have to worry about are non-object variables like buffers created with C's malloc()
.
Like init
, you don't have to implement a dealloc
method if your object doesn't need any special handling before it is released. This is often the case for automatic reference-counting environments.
Private Methods
A big hurdle for C# developers transitioning to Objective-C is the apparent lack of private methods. Unlike C#, all methods in an Objective-C class are accessible to third parties; however, it is possible to emulate the behavior of private methods.
Remember that clients only import the interface of a class (i.e. the header files)-they should never see the underlying implementation. So, by adding new methods inside of the implementation file without including them in the interface, we can effectively hide methods from other objects. Albeit, this is more convention-based than "true" private methods, but it's essentially the same functionality: trying to call a method that's not declared in an interface will result in a compiler error.
For example, let's say you needed to add a private prepareToShoot
method to the Ship
class. All you have to do is omit it from Ship.h
while adding it to Ship.m
:
// Ship.h @interface Ship : NSObject @property (weak) Person *captain; - (void)shoot; @end
This declares a public method called shoot
, which will use the private prepareToShoot
method. The corresponding implementation might look something like:
// Ship.m #import "Ship.h" @implementation Ship { BOOL _gunIsReady; } @synthesize captain = _captain; - (void)shoot { if (!_gunIsReady) { [self prepareToShoot]; _gunIsReady = YES; } NSLog(@"Firing!"); } - (void)prepareToShoot { // Execute some private functionality. NSLog(@"Preparing the main weapon..."); } @end
As of Xcode 4.3, you can define private methods anywhere in the implementation. If you use the private method before the compiler has seen it (as in the previous example), the compiler checks the rest of the implementation block for the method definition. Prior to Xcode 4.3, you had to either define a private method before it was used elsewhere in the file, or forward-declare it with a class extension.
Class extensions are a special case of categories, which are presented in the upcoming chapter. Just as there is no way to mark a method as private, there is no way to mark a method as protected; however, as we'll see in the next chapter, categories provide a powerful alternative to protected methods.
Selectors
Selectors are Objective-C's way of representing methods. They let you dynamically "select" one of an object's methods, which can be used to refer to a method at run time, pass a method to another function, and figure out whether an object has a particular method. For practical purposes, you can think of a selector as an alternative name for a method.
Internally, Objective-C uses a unique number to identify each method name that your program uses. For instance, a method called sayHello
might translate to 4984331082
. This identifier is called a selector, and it is a much more efficient way for the compiler to refer to methods than their full string representation. It's important to understand that a selector only represents the method name-not a specific method implementation. In other words, a sayHello
method defined by the Person
class has the same selector as a sayHello
method defined by the Ship
class.
The three main tools for working with selectors are:
-
@selector()
- Return the selector associated with a source-code method name. -
NSSelectorFromString()
- Return the selector associated with the string representation of a method name. This function makes it possible to define the method name at run time, but it is less efficient than@selector()
. -
NSStringFromSelector()
- Return the string representation of a method name from a selector.
As you can see, there are three ways to represent a method name in Objective-C: as source code, as a string, or as a selector. These conversion functions are shown graphically in the following figure:
Selectors are stored in a special data type called SEL
. The following snippet demonstrates the basic usage of the three conversion functions shown in the previous figure:
int main(int argc, const char * argv[]) { @autoreleasepool { SEL selector = @selector(sayHello); NSLog(@"%@", NSStringFromSelector(selector)); if (selector == NSSelectorFromString(@"sayHello")) { NSLog(@"The selectors are equal!"); } } return 0; }
First, we use the @selector()
directive to figure out the selector for a method called sayHello
, which is a source-code representation of a method name. Note that you can pass any method name to @selector()
-it doesn't have to exist elsewhere in your program. Next, we use the NSStringFromSelector()
function to convert the selector back into a string so we can display it in the output panel. Finally, the conditional shows that selectors have a one-to-one correspondence with method names, regardless of whether you find them through hard-coded method names or strings.
Method Names and Selectors
The previous example uses a simple method that takes no parameters, but it's important to be able to pass around methods that do accept parameters. Recall that a method name consists of the primary method name concatenated with all of the parameter names. For example, a method with the signature
- (void)sayHelloToPerson:(Person *)aPerson withGreeting:(NSString *)aGreeting;
would have a method name of:
sayHelloToPerson:withGreeting:
This is what you would pass to @selector()
or NSSelectorFromString()
to return the identifier for that method. Selectors only work with method names (not signatures), so there is not a one-to-one correspondence between selectors and signatures. As a result, the method name in the last example will also match a signature with different data types, including the following:
- (void)sayHelloToPerson:(NSString *)aName withGreeting:(BOOL)useGreeting;
The verbosity of Objective-C's naming conventions avoids most confusing situations; however, selectors for one-parameter methods can still be tricky because appending a colon to the method name actually changes it into a completely different method. For example, in the following sample, the first method name doesn't take a parameter, while the second one does:
sayHello sayHello:
Again, naming conventions go a long way toward eliminating confusion, but you still need to make sure you know when it's necessary to add a colon to the end of a method name. This is a common issue if you're new to selectors, and it can be hard to debug, as a trailing colon still creates a perfectly valid method name.
Performing Selectors
Of course, recording a selector in a SEL
variable is relatively useless without the ability to execute it later on. Since a selector is merely a method name (not an implementation), it always needs to be paired with an object before you can call it. The NSObject
class defines a performSelector:
method for this very purpose.
[joe performSelector:@selector(sayHello)];
This is the equivalent of calling sayHello
directly on joe
:
[joe sayHello];
For methods with one or two parameters, you can use the related performSelector:withObject:
and performSelector:withObject:withObject:
methods. The following method implementation:
- (void)sayHelloToPerson:(Person *)aPerson { NSLog(@"Hello, %@", [aPerson name]); }
could be called dynamically by passing the aPerson
argument to the performSelector:withObject:
method, as demonstrated here:
[joe performSelector:@selector(sayHelloToPerson:) withObject:bill];
This is the equivalent of passing the parameter directly to the method:
[joe sayHelloToPerson:bill];
Likewise, the performSelector:withObject:withObject:
method lets you pass two parameters to the target method. The only caveat with these is that all parameters and the return value of the method must be objects-they don't work with primitive C data types like int
, float
, etc. If you do need this functionality, you can either box the primitive type in one of Objective-C's many wrapper classes (e.g., NSNumber
) or use the NSInvocation object to encapsulate a complete method call.
Checking for the Existence of Selectors
It's not possible to perform a selector on an object that hasn't defined the associated method. But unlike static method calls, it's not possible to determine at compile time whether performSelector:
will raise an error. Instead, you have to check if an object can respond to a selector at run time using the aptly named respondsToSelector:
method. It simply returns YES
or NO
depending on whether the object can perform the selector:
SEL methodToCall = @selector(sayHello); if ([joe respondsToSelector:methodToCall]) { [joe performSelector:methodToCall]; } else { NSLog(@"Joe doesn't know how to perform %@.", NSStringFromSelector(methodToCall)); }
If your selectors are being dynamically generated (e.g., if methodToCall
is selected from a list of options) or you don't have control over the target object (e.g., joe
can be one of several different types of objects), it's important to run this check before trying to call performSelector:
.
Using Selectors
The whole idea behind selectors is to be able to pass around methods just like you pass around objects. This can be used, for example, to dynamically define an "action" for a Person
object to execute later on in the program. For example, consider the following interface:
Included code sample: Selectors
@interface Person : NSObject @property (copy) NSString *name; @property (weak) Person *friend; @property SEL action; - (void)sayHello; - (void)sayGoodbye; - (void)coerceFriend; @end
Along with the corresponding implementation:
#import "Person.h" @implementation Person @synthesize name = _name; @synthesize friend = _friend; @synthesize action = _action; - (void)sayHello { NSLog(@"Hello, says %@.", _name); } - (void)sayGoodbye { NSLog(@"Goodbye, says %@.", _name); } - (void)coerceFriend { NSLog(@"%@ is about to make %@ do something.", _name, [_friend name]); [_friend performSelector:_action]; } @end
As you can see, calling the coerceFriend
method will force a different object to perform some arbitrary action. This lets you configure a friendship and a behavior early on in your program and wait for a particular event to occur before triggering the action:
#import <Foundation/Foundation.h> #import "Person.h" NSString *askUserForAction() { // In the real world, this would be capture some // user input to determine which method to call. NSString *theMethod = @"sayGoodbye"; return theMethod; } int main(int argc, const char * argv[]) { @autoreleasepool { // Create a person and determine an action to perform. Person *joe = [[Person alloc] init]; joe.name = @"Joe"; Person *bill = [[Person alloc] init]; bill.name = @"Bill"; joe.friend = bill; joe.action = NSSelectorFromString(askUserForAction()); // Wait for an event... // Perform the action. [joe coerceFriend]; } return 0; }
This is almost exactly how user-interface components in iOS are implemented. For example, if you had a button, you would configure it with a target object (e.g., friend
), and an action (e.g., action
). Then, when the user eventually presses the button, it can use performSelector:
to execute the desired method on the appropriate object. Allowing both the object and the method to vary independently affords significant flexibility-the button could literally perform any action with any object without altering the button's class in any way. This also forms the basis of the Target-Action design pattern, which is heavily relied upon in the iOS Succinctly companion book.
Summary
In this chapter, we covered instance and class methods, along with some of the most important built-in methods. We worked closely with selectors, which are a way to refer to method names as either source code or strings. We also briefly previewed the Target-Action design pattern, which is an integral aspect of iOS and OS X programming.
The next chapter discusses an alternative way to create private and protected methods in Objective-C.
This lesson represents a chapter from Objective-C Succinctly, a free eBook from the team at Syncfusion.
Comments