Messaging
This post introduces messaging within Objective-C. Messaging is the terminology for invoking methods on an object. The format for a message expression is as follows (the brackets are required):
[object method]
or in Objective-C parlance
[receiver message]
Here’s a simple example:
// Create an instance of SomeClass object
// We'll cover this later...
SomeClass *ptr = [[SomeClass alloc] init];
// Send the message 'printInstanceVars' to the 'ptr' receiver
[ptr printInstanceVars];
If we want to pass an argument as part of the message (that is, pass a parameter to the method), we send a message that looks like this:
[receiver message:argument]
For example, assume setStr and setX are two methods in the SomeClass object and ptr is a pointer an instance of SomeClass . Here is how we might pass arguments to each method.
[ptr setStr:@"Testing"];
[ptr setX:2008];
Side note: The @ symbol at the front of @”Testing” string is convenience method that converts the given string to an NSString object, which in the case of the setStr method, is the required type for the parameter.
Nest Messages
We can also nest messages, as shown below. In this example, we first pass a message to the NSDate object. This is a class object with a factory method ‘date’ for creating a new NSDate object. If that makes no sense, don’t worry about it. Think of it as nothing more than creating a new NSDate object that holds the current date and time. The return value of inner message (the NSDate object) is the argument for setDate message. What we’ve done here is to create a new date object and pass it as an argument to the setDate message of the receiver (ptr ). Sounds more confusing than it is.
[ptr setDate:[NSDate date]];
Multiple Arguments
Taking this another step further, we can pass multiple arguments along
with our message. The message below takes three arguments, a string,
date and integer. Sometimes it easier to read the method aloud to get
this jist of what’s up. In this case, “pass a message to the ptr
receiver that sets a string, and a date and an integer.”
[ptr setStr:@"A new test..." andDate:[NSDate date] andInteger:99];
Here is how we define the message in the interface file:
-(void) setStr:(NSString *)str andDate:(NSDate *)date andInteger:(int)x;
And here is the implementation of the setStr() method which accepts three arguments:
-(void) setStr:(NSString *)strInput andDate:(NSDate *)dateInput
andInteger:(int)xInput
{
[self setStr:strInput];
[self setDate:dateInput];
[self setX:xInput];
}
Let me point out a few things about this method. First, notice how in the definition I used the names str , date and x . However, in the implementation, I used the names strInput , dateInput and xInput . The first thing to understand is that you can do this. The reason for doing so, is that in the class where setStr() is defined, I have instance variables with the names str , date and x . If I didn’t change the names in the actual implementation, I’d get a compiler warning that a local declaration hides an instance variable.
In this code example, the simplest thing to do is to change the variable names in the method to make it obvious what variables are referring to what. This may not be the best naming scheme for your applications, however, I wanted to point out the flexibility as to how you define and implement methods and their arguments. Obviously, do what makes the most sense for your specific needs and at the same time, results in the most readable code.
Also, note that this version of setStr() does nothing more than send a message as follows: the receiver is current object instance (self), the message is a setter method and the argument to each message is the appropriate parameter passed in to setStr() .
Using the format/syntax of Objective-C takes some getting used to. However, once you do, you’ll find that it’s easy to read code that has had some thought put into the names of message and the parameters.
Variable Number of Arguments
The last topic is creating messages with a variable number of
arguments. Let’s start with the interface definition of a method that
accepts a variable number of arguments:
-(void) printInstanceVars:(id)input, ...;
In this example, I declare on parameter that is of type ‘id’, which can represent any object. The implementation of the method looks as follows:
-(void) printInstanceVars:(id)input, ...
{
id currentObject;
va_list argList;
int objectCount = 1;
if (input)
{
NSLog(@"\n Object #%d is: %@\n", objectCount++, input);
va_start(argList, input);
while (currentObject = va_arg(argList, id))
NSLog(@"\n Object #%d is: %@\n", objectCount++, currentObject);
va_end(argList);
}
}
This method will print out each argument using NSLog(). The assumption is made that the last argument will be nil. Here is how you might call the method, passing in two objects. What’s happening here is that I am sending getter messages, if you will, to the ‘ptr’ receiver. Each of these getters returns an object. Notice the nil value as the last parameter.
[ptr printInstanceVars:[ptr str], [ptr date], nil];
Now, this example is a little contrived in that there is probably little value in passing in instance variables from an object, as I’ve done here. However, introducing more classes at this point may confuse more than help. If nothing else, hopefully you get the jist of how this works.
I haven’t come upon the case where this is necessary in an Objective-C application, however, when programming in C, this was quite handy when reading command line parameters. So,if you ever find the opportunity, now you’ll know how. And if you are into trivia, variable argument methods in Objective-C are referred to as variadic methods, go figure.
Many of the things that I’ve covered here are much better understood by looking at a real example. I’ve attached the Xcode project that I created to learn this stuff. You best bet is to download, open the project and mess around with the code.
Here is the main method of the example, so you can get an idea of how I tested each of the above examples.
int main(int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SomeClass *ptr = [[SomeClass alloc] init];
// Simple message
[ptr printInstanceVars];
// Passing a single argument
[ptr setStr:@"Testing"];
[ptr setX:2008];
[ptr printInstanceVars];
// Nesting of messages
[ptr setDate:[NSDate date]];
[ptr printInstanceVars];
// Passing multiple arguments
[ptr setString:@"A new test..." andDate:[NSDate date] andInteger:99];
[ptr printInstanceVars];
// Passing variable number of argument
[ptr printInstanceVars:[ptr str], [ptr date], nil];
// This won't work...
// [ptr printInstanceVars:[ptr str], [ptr date], [ptr x], nil];
[ptr printInstanceVars:[ptr str], [ptr date], [NSNumber numberWithInt:[ptr x]], nil];
[ptr release];
[pool drain];
return 0;
}
Download the Xcode Project
Download the Xcode project and take some time to tinker.
Once you’ve got a good handle on the overall application, look at line 27 above. Make any sense? Here’s the deal: the variable length method is expecting objects to be passed in. The code in line 26 will generate a runtime error given the message [ptr x] returns an integer (not an object). The way around this is to create an NSNumber object by sending a message to the NSNumber receiver, with the message ‘numberWithInt’ passing in as the parameter the integer returned from the getter message sent to the ‘ptr’ receiver. If all that makes sense, you’re well on your way :)
For completeness, the entire code listing and a screenshot are shown below:
// ===========================
// = SomeClass.h =
// ===========================
#import <Foundation/Foundation.h>
@interface SomeClass : NSObject
{
NSString *str;
NSDate *date;
int x;
}
// Getters
-(int) x;
-(NSString *) str;
-(NSDate *) date;
// Setters
-(void) setStr:(NSString *)input;
-(void) setDate:(NSDate *)input;
-(void) setX:(int)input;
-(void) setStr:(NSString *)str andDate:(NSDate *)date andInteger:(int)x;
// Other
-(void) printInstanceVars;
-(void) printInstanceVars:(id)input, ...;
-(void) dealloc;
@end
#import "SomeClass.h"
#import <stdio.h>
// ================================
// = SomeClass.m =
// ================================
@implementation SomeClass
// =================
// = Getter methods =
// =================
-(int) x
{
return x;
}
-(NSString *) str
{
return str;
}
-(NSDate *) date
{
return date;
}
// =================
// = Setter Methods =
// =================
-(void) setStr:(NSString *)foo
{
[foo retain];
[str release];
str = foo;
}
-(void) setDate:(NSDate *)input
{
[input retain];
[date release];
date = input;
}
-(void) setX:(int)input
{
x = input;
}
-(void) setStr:(NSString *)strInput andDate:(NSDate *)dateInput andInteger:(int)xInput
{
[self setStr:strInput];
[self setDate:dateInput];
[self setX:xInput];
}
// ================================
// = Print the instance vars =
// ================================
-(void) printInstanceVars
{
// Use the getter method of the ’self’ object to print object instance variables
// NSLog(@"\n x: %d\n str: %@\n date: %@\n", [self x], [self str], [self date]);
// The class can directly access the instance variables (versus calling message as above)
NSLog(@"\n x: %d\n str: %@\n date: %@\n", x, str, date);
}
-(void) printInstanceVars:(id)input, ...
{
id currentObject;
va_list argList;
int objectCount = 1;
if (input)
{
NSLog(@"\n Object #%d is: %@\n", objectCount++, input);
va_start(argList, input);
while (currentObject = va_arg(argList, id))
NSLog(@"\n Object #%d is: %@\n", objectCount++, currentObject);
va_end(argList);
}
}
// ====================================
// = Dealloc all object instance vars =
// ====================================
-(void) dealloc
{
// No release needed of the integer instance variable ‘x’
[str release];
[date release];
[super dealloc];
}
@end
// ===========================
// = Messaging.m =
// ===========================
#import <Foundation/Foundation.h>
#import "SomeClass.h"
int main(int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SomeClass *ptr = [[SomeClass alloc] init];
// Simple message
[ptr printInstanceVars];
// Passing a single argument
[ptr setStr:@"Testing"];
[ptr setX:2008];
[ptr printInstanceVars];
// Nesting of messages
[ptr setDate:[NSDate date]];
[ptr printInstanceVars];
// Passing multiple arguments
[ptr setStr:@"A new test..." andDate:[NSDate date] andInteger:99];
[ptr printInstanceVars];
// Passing variable number of argument
[ptr printInstanceVars:[ptr str], [ptr date], nil];
// This won't work...
// [ptr printInstanceVars:[ptr str], [ptr date], [ptr x], nil];
[ptr printInstanceVars:[ptr str], [ptr date], [NSNumber numberWithInt:[ptr x]], nil];
[ptr release];
[pool drain];
return 0;
}
Source : http://macdevelopertips.com/objective-c/objective-c-messaging.html