views:

684

answers:

1

In order to better understand the startup, event queue, and methods within my application I'm trying to write a program that does two things: Play a beep at the startup and every time the user hits a button. So far it only plays when the user hits the button. I know there may be multiple ways to get the startup beep to play, but in order to work with initialization code I want to do it by calling my beep method from within the applicationDidFinishLaunching method of the AppDelegate.m file.

Here is my code:

Log.h

#import <Cocoa/Cocoa.h>


@interface Log : NSObject {

    IBOutlet id button;

}
-(void)beepAndLog;
-(IBAction)buttonPressed:(id)sender;

@end

Log.m

#import "Log.h"


@implementation Log

-(void)beepAndLog {

    NSLog(@"The Method Was Called!");
    NSBeep();

}

-(IBAction)buttonPressed:(id)sender {

    [self beepAndLog];
}
@end

And the applicationDidFinishLaunching method looks like this:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    [Log beepAndLog];

}

In the applicationDidFinishLaunching method, however, XCode warns me that

'Log' may not respond to '+beepAndLog'

and indeed, there is no beep and the log reads as follows:

MethodResponse[11401:a0f] +[Log beepAndLog]: unrecognized selector sent to class 0x100002100

("MethodResponse" is the name of my project, btw)

I'm unsure why Log wouldn't respond to beepAndLog, seeing as that's one of its methods. Am I calling it incorrectly? I have a feeling this will be painfully obvious to you more experienced people. I'm a newbie. Any help would be appreciated! Thanks!

+1  A: 

There are two possibilities. Either you defined beepAndLog as an instance method, when you wanted a class method, or you want to call it on an instance when you called it on the class.

To change it to a class method, change the header to read:

+(void)beepAndLog;

and the implementation:

+(void)beepAndLog {
    NSLog(@"The Method Was Called!");
    NSBeep();
}

For the other solution, make sure you have an instance of class Log around (probably a singleton), and do something like:

[[Log logInstance] beepAndLog];

from your notification method. The Log class would need to look something like this:

Log.h:

#import <Cocoa/Cocoa.h>

@interface Log : NSObject {
    IBOutlet id button;
}

+(Log *)logInstance;

-(void)beepAndLog;
-(IBAction)buttonPressed:(id)sender;

@end

Log.m:

#import "Log.h"

Log *theLog = nil;

@implementation Log

+(Log *)logInstance
{
    if (!theLog) {
        theLog = [[Log alloc] init];
        // other setup (like hooking up that IBAction)
    }
    return theLog;
}

-(void)beepAndLog {
    NSLog(@"The Method Was Called!");
    NSBeep();
}

-(IBAction)buttonPressed:(id)sender {
    [[Log logInstance] beepAndLog];
}
Carl Norum
That was a really fast response. Thanks a lot, it worked perfectly. I guess I'll have to be looking into the differences between those. Thanks for taking the time to help a beginner!
Arthur Skirvin
No problem. Class methods (`+` methods) are invoked on the class itself. Instance methods (`-` methods) are to be used on objects instantiated from the class. Check out the Objective-C documentation for more details.
Carl Norum
Thanks again :)
Arthur Skirvin
Woooaaaahhhh....Scratch that. I got too excited that it was beeping and logging at the startup that I didn't notice that the button no longer works. I'm now warned that " 'Log may not respond to '-beepAndLog' " in the IBAction method next to [self beepAndLog]; GRRRR...Now what?
Arthur Skirvin
Should I add both a class and instance method of beepAndLog, or is there a more elegant solution?
Arthur Skirvin
You can mix and match class and instance methods, yes. Making a singleton might be the best solution for you. Your class would then have only instance methods, except for the class method that returns the single instance to the caller. See my second example: `logInstance` is a class method returning the one and only `Log` instance, then `beepAndLog` would be an instance method.
Carl Norum
I extended the second example to be more along the singleton lines. As I say, you may want to mix and match.
Carl Norum
Arthur Skirvin: Your action method, like all action methods, is an instance method. Thus, within that method, `self` is an instance. Thus, `[self beepAndLog]` is an instance message, and since you changed `beepAndLog` to be a class method, the message fails. Either make it an instance method and have an instance, or keep it a class method and change the message expression to go to the class.
Peter Hosey
Also, singletons are often a code smell. Avoid them whenever there's another way. (There is plenty of text on the web describing why singletons are bad.) In this case, you could have an instance of Log within the nib, and give the app delegate an outlet to that instance. Or, have the app delegate create and own the Log instance, and have the app delegate, not the Log, respond to the action message and be the button's target. I would do the latter, since I don't think the Log is part of your UI.
Peter Hosey
Thanks, guys. This really helps.
Arthur Skirvin