Advertisement
iOS SDK

Objective-C Succinctly: Methods

by

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.

Figure 25 Attempting to call a private method

Attempting to call a "private" method

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.

Figure 26 Developers representation of a method vs Objective-Cs representation

Developers' representation of a method vs. Objective-C's representation

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:

Figure 27 Converting between source code strings and selectors

Converting between source code, strings, and selectors

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.
Related Posts
  • Code
    iOS SDK
    Objective-C Succinctly: Blocks0e5ds8 preview image@2x
    Blocks are actually an extension to the C programming language, but they are heavily utilized by Apple's Objective-C frameworks. They are similar to C#'s lambdas in that they let you define a block of statements inline and pass it around to other functions as if it were an object.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Protocols0e5ds8 preview image@2x
    In Objective-C, a protocol is a group of methods that can be implemented by any class. Protocols are essentially the same as interfaces in C#, and they both have similar goals. They can be used as a pseudo-data type, which is useful for making sure that a dynamically-typed object can respond to a certain set of messages. And, because any class can "adopt" a protocol, they can be used to represent a shared API between completely unrelated classes.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Categories and Extensions0e5ds8 preview image@2x
    Categories are an Objective-C language feature that let you add new methods to an existing class, much like C# extensions. However, do not confuse C# extensions with Objective-C extensions. Objective-C's extensions are a special case of categories that let you define methods that must be declared in the main implementation block.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Memory Management0e5ds8 preview image@2x
    Memory must be allocated for each object your application uses, and it must be deallocated when your application is done with it to ensure your application is using memory as efficiently as possible. It's important to understand Objective-C's memory management environment to ensure your program doesn't leak memory or try to reference objects that no longer exist.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Properties0e5ds8 preview image@2x
    Now that we've explored what data types are available, we can talk about actually using them in a productive manner. We learned how to declare properties in Hello, Objective-C, but this chapter dives deeper into the nuances behind public properties and instance variables. First, we'll take a quick look at the basic syntax of properties and instance variables, and then we'll discuss how to use behavior attributes to modify accessor methods.Read More…
  • Code
    iOS SDK
    Objective-C Succinctly: Hello Objective-C0e5ds8 preview image@2x
    This chapter is designed to help you acclimate to Objective-C programming style. By the end of this chapter, you will be able to instantiate objects, create and call methods, and declare properties. Remember that the goal is to provide a very brief survey of the major object-oriented aspects of Objective-C, not a detailed description of each component. Later chapters fill in many of the conceptual details omitted from this chapter.Read More…