Objective-C "messages" - what's the right way to read it?
Asked Answered
A

5

8

You can declare a method in objective-c and name each parameter twice, basically.

I get the idea that this is powerful, but I'm not quite sure how to use it yet...

When John Greets Kelly:

[ p Greet:"John" toPerson:"Kelly" greetWith:"hey babe" ] ;

Something about it doesn't read naturally. I'm not sure if that's how an experienced objective-c programmer would write that "message".

Can someone explain the reason for two names for each parameter and possibly a more useful example of how this can be used effectively to put meaning in the program?

Also something bothers me and that is the name of the first parameter is basically the same as the name of the 'message'. How do you resolve that in writing meaningful and understandable method/'message names'?

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
}

-(void)Greet:(char*)from toPerson:(char*)to greetWith:(char*)greeting ;

@end

@implementation Person

-(void)Greet:(char*)from toPerson:(char*)to greetWith:(char*)greeting ;
{
  printf( "%s says %s to %s\n", from, greeting, to ) ;
}

@end



int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];


  Person * p = [ Person alloc ] ;

  [ p Greet:"John" toPerson:"Kelly" greetWith:"hey babe" ] ;
  [ p Greet:"Kelly" toPerson:"John" greetWith:"get bent" ] ;

  [ p release ] ;



  [pool drain];
  return 0;
}

Amphibology answered 8/11, 2009 at 20:21 Comment(1)
Regarding edit: I clicked downvote by mistake when I went to favorite, and then it wouldn't let me reverse it (weird bug?). I edited your answer so I could swap it.Eleneeleni
E
20

First things first: as a stylistic note, bring your braces together:

[ Person alloc ]

should be

[Person alloc]

I also note that you're forgetting to initialize Person when you alloc it, you should be using:

Person *p = [[Person alloc] init];

Getting the feel for how to declare methods takes a little bit of time. Examining how the framework names its methods is useful. For your specific example, I think you're overengineering. You're looking for something like this:

Person *john = [[Person alloc] initWithName:@"John"];
Person *kelly = [[Person alloc] initWithName:@"Kelly"];

[john greetPerson:kelly withGreeting:@"Hey babe."];
[kelly greetPerson:john withGreeting:@"Get bent."];

Notice that I've not capitalized the g in greetPerson, either. That's a stylistic convention of Objective-C.

Don't forget that an object has its own identity, so rarely do you need to instruct an object (meant to represent a person) who it is before it talks to someone. When you write a message, it should read like English. When you get into multiple arguments -- admittedly, rare -- start thinking with commas:

[john sendEmailToPerson:kelly withSubject:subject body:body attachments:nil];

See how that flows? And even that leaves some to be desired, I don't have this mastered yet either. Give it time.

A very useful document on this is Apple's Coding Guidelines for Cocoa.


Also, get yourself out of the C trap. Here's how I'd write your entire program (I'm introducing a boatload of concepts, so don't expect to understand all of it):

#import <Foundation/Foundation.h>

@interface Person : NSObject {
    NSString *name;
}

@property (copy) NSString *name;

- (id)init;
- (id)initWithName:(NSString *)nm;
- (void)greetPerson:(Person *)who withGreeting:(NSString *)grt;

@end

@implementation Person
@synthesize name;

- (id)init {
    if (self = [super init]) {
        name = @"James Bond";          // not necessary, but default
    }                                  // values don't hurt.
    return self;
}
- (id)initWithName:(NSString *)nm {
    if (self = [self init]) {
       name = [nm copy];
    }
    return self;
}

- (void)greetPerson:(Person *)who withGreeting:(NSString *)grt {
    NSLog(@"%@ says '%@' to %@", self.name, grt, who.name);
}

- (void)dealloc {
    [name release];
    [super dealloc];
}

@end

int main(int argc, const char * argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    Person *john = [[Person alloc] initWithName:@"John"];
    Person *kelly = [[Person alloc] initWithName:@"Kelly"];

    [john greetPerson:kelly withGreeting:@"StackOverflow is great, isn't it?"];
    [kelly greetPerson:john withGreeting:@"Weren't we supposed to flirt?"];

    [john release];
    [kelly release];

    [pool drain];
    return 0;
}

This code is completely untested, so if it works without a hitch I'll be duly impressed.

Eleneeleni answered 8/11, 2009 at 20:28 Comment(12)
Apple's style guidelines suggest you should not use "and" for subsequent parts of your method name. (From the document you just linked: "Don’t use “and” to link keywords that are attributes of the receiver.") So that selector should just be sendEmailToPerson:withSubject:body:attachments:.Ader
@Chuck: See? Told you I'm not an expert. Editing out.Eleneeleni
Good Answer. One additional note that you put in your code, but didn't mention: The first part of a selector shouldn't be capitalized. So you would [john greetPerson] instead of [john GreetPerson:]. I know its nitpicking, but Objective-C programers are very particular about the syntax and style guidelines! The capital Greet is very Javaish.Wehrmacht
Great answer! Very clearly written. If you want to perfect the example you shouldn't use self.name = nm but only name = nm. This is for the same reason that you shouldn't use self.name = nil in the dealloc method: the accessors could have unwanted side effects.Egression
@gs: This is my first whack at dot syntax, I used self.name = nm so the retain would happen. Was this incorrect?Eleneeleni
@Jed Smith: I'm fairly convinced it should read self.name = [nm retain]. This has the nice benefit of having balanced retain/release. You may want to take a look at mmalc's answer here: #193221 (Ignore the accepted answer, it's not very good.) and this question here: #1283919Egression
I made some changes: Almost all -init methods should go through the simple -init method. One notable exception is -initWithCoder. Your init method was missing a return type. It's better to set the property's attribute to copy than retain, this allows assignment of NSMutableStrings. I shortened your dialog so that there's no scrollbar in the answer.Egression
I was wrong about using name = [nm retain], there too one should use name = [nm copy] so that mutable strings are converted to inmutable ones. Already inmutable strings are just retained.Egression
@gs: Can you explain why you're bypassing the setter?Eleneeleni
@Jed Smith: Using accessors in either -init or -dealloc can cause unwanted side effects. Some getters may depend on other instance variables that haven't yet been initialized. It's best to just avoid it. Read lists.apple.com/archives/objc-language/2008/Oct/msg00009.html. Also, the Objective-C 2.0 Guide from Apple discourages the use of accessors in -init. One such problematic code can be seen here: #193221Egression
@gs: Ah, cool. This is why I've been hesitant on dot syntax. Thanks.Eleneeleni
@Jed Smith: Everywhere else you should use accessors, even in instance methods. The only reason not to use them in normal methods is the additional method call that is necessary, but most application's won't notice the additional 10ns that this uses.Egression
S
4

Other people have covered the most important points, so I'll just weigh in on some of the supplementary questions:

Also something bothers me and that is the name of the first parameter is basically the same as the name of the 'message'. How do you resolve that in writing meaningful and understandable method/'message names'?

The most common way of dealing with this is to make the method name a combination of "what it is/does" and the label for the first parameter. Example:

 NSColor * color = [NSColor colorWithDeviceRed:0.5 green:0.5 blue:0.5 alpha:0.5];

Can someone explain the reason for two names for each parameter and possibly a more useful example of how this can be used effectively to put meaning in the program?

The quick answer is that the two names are intended for different audiences; one is for the user of the method, the other is for the author of the method. Consider the above example again. The method declaration looks like this:

+ (NSColor *)colorWithDeviceRed:(CGFloat)red
                          green:(CGFloat)green
                           blue:(CGFloat)blue
                          alpha:(CGFloat)alpha

When a user calls that method, they are concerned with the first labels (the ones before the colon). You can see in my first example, where the channel values are passed as numeric constants, that only the labels are seen in code.

The actual parameter names (the parts after the type), are used within the method definition, and as such are really only there for the programmer who writes the method, since those are the variables that will be available within the method body itself.

Shillelagh answered 8/11, 2009 at 20:43 Comment(0)
S
2

The first "part" of the method is the selector, for your example this includes -greet:toPerson:greetWith:, this is the actual name of the method.

The second "part" are the parameters of the method, in your example they are to, greeting.

This is akin to something like C where you would have

int greet(string to, string greeting) {
    print(greeting, to);
}

I should also mention, you're likely going to want to work with NSString * instead of char * and NSLog() instead of printf() (don't worry, it works almost exactly the same).

Sensitive answered 8/11, 2009 at 20:29 Comment(0)
C
0

Probably it'd be:

[personJohn greetPerson:@"Kelly" withGreeting:@"hey babe"];

The person object is already John and he's greeting another person object, which I wouldn't define as a string as you did, but instead as another instance of class Person, and then you could do greetWithString.

[personJohn greetPerson:personKelly withGreeting:@"hey babe"];

You're not defining the parameters twice, that's where you're confused. If you know C++, you usually have:

void greetPerson(Person thePerson, string greeting);

as the function prototype for example. Then you would call it like so:

greetPerson(personKelly, "hey babe");

Now, all objective-c is doing is making it easy on you by being self-documenting. Instead of simply putting the parameters to the functions, you name them before you state them, so the above call would be:

greetPerson(Person:personKelly, greeting:"hey babe");

given the function prototype posted above. This way when you read the code, you know what each parameter is doing, hence being self documenting. It might seem tedious at first, but code readability is greatly increased.

Corcyra answered 8/11, 2009 at 20:29 Comment(0)
B
0

The name of the method you describe above is "Greet:toPerson:greetWith:". The colons are part of the name. The parameters (and their type specifiers) are not.

Style note: don't start a method name with an uppercase character unless you're referring to an acronym (like "URL".)

Blanchblancha answered 8/11, 2009 at 20:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.