PDA

View Full Version : selector again




liptonlover
Sep 2, 2008, 12:00 PM
This problem has been frustrating me for a while now. I was determined to figure it out without help. But I'm thinking now either I'm just plain dumb, or it's a dumb mistake I'm not seeing. A while back I asked about selectors. I didn't understand a word people were saying. But now, having read the Apple Objective-C documentation, especially the one pertaining to selectors, and after doing some small tests, I thought I could fix my problem. But I can't.

So the situation:
Here's all four class files (two classes total).


#import <Cocoa/Cocoa.h>
#import "ESAnimation.h"

@interface AppController : NSObject {
IBOutlet id field;
ESAnimation *animate;
}
- (IBAction)button:(id)sender;
- (void)draw;
@end



#import "AppController.h"

@implementation AppController

- (IBAction)button:(id)sender {
NSLog(@"button pressed");
animate=[[ESAnimation alloc] init];
[animate animateIntVariable:1 toValue:1 atSpeed:1.0 inIncrementsOf:1 receiver:self selector:@selector(draw)];
}
- (void)draw {
NSLog(@"draw invoked");
[field setStringValue:@"success!"];
}
@end



#import <Cocoa/Cocoa.h>


@interface ESAnimation : NSObject {
id userObject;
SEL userSelector;
NSTimer *timer;
}
- (void)animateIntVariable:(int)aVariable toValue:(int)aValue atSpeed:(float)aSpeed inIncrementsOf:(int)anIncrement receiver:(id)aReceiver selector:(SEL)aSelector;
- (void)incrementInt:(NSTimer *)aTimer;
@end



#import "ESAnimation.h"


@implementation ESAnimation

- (void)animateIntVariable:(int)aVariable toValue:(int)aValue atSpeed:(float)aSpeed inIncrementsOf:(int)anIncrement receiver:(id)userObject selector:(SEL)userSelector {
NSLog(@"animateIntVariable invoked");
[self incrementInt:nil];
}
- (void)incrementInt:(NSTimer *)aTimer {
NSLog(@"incrementInt invoked");
[userObject performSelector:userSelector];
}
@end


I know from my logs that everything works fine until it gets to incrementInt. incrementInt gets called, but that's as far as it goes. draw doesn't get called. I'm proud of myself because I identified a possible problem... when I call animateIntVariable: ... I pass self as an argument for receiver. The problem is, I don't know what else to put there if that is wrong. If it's not the problem, I'm completely stumped.
Thanks for all your help so far!
Nate



robbieduncan
Sep 2, 2008, 12:23 PM
You haven't "saved" userObject or userSelector anywhere in animateIntVariable:...

You pass them in so those variables are in scope for that method, but no methods it calls. You probably want to add instance variables to that class, set them as properties, synthesize them and call self.userObject = userObject and self.userSelector = userSelector before calling incrementInt.

liptonlover
Sep 2, 2008, 03:35 PM
Thanks! Problem solved.
I thought using instance variables declared in the header would work... apparently not.
Nate

robbieduncan
Sep 2, 2008, 04:01 PM
Thanks! Problem solved.
I thought using instance variables declared in the header would work... apparently not.
Nate

All that does is reserve space for them in the object. It's still up to you to deal with setting them when you want to...

lee1210
Sep 2, 2008, 04:11 PM
Thanks! Problem solved.
I thought using instance variables declared in the header would work... apparently not.
Nate

This is an issue with scope... calling a method argument the same thing as a field of your class doesn't make them the same, hence the [self setuserSelector:userSelector]/self.userSelector = userSelector; ... this resolves the scope by explicitly saying to do something with the property instead of the method-local variable. It might be easiest to just change the name of one of them to remove the ambiguity.

Right now it looks like you are just trying to get your method called, which is absolutely fine, but note that performSelector is passing no arguments, and it looks like you have a number of arguments you will eventually want to pass to draw. You have a few options to handle this... you can pass up to 2 objects to your method using performSelector:withObject:withObject:. The bad news is it looks like you'll have 4 things to pass and none of them are objects. One possible way to resolve this is just have draw take one id, that is actually a class you build whose only purpose is to pass this stuff around. So it would be called AnimateInformation and have 4 fields:
int variableToAnimate;
int animateTo;
float animationSpeed;
int animationIncrement;


Then you can just pass an AnimateInformation instance (as an id) to draw using performSelector:withObject:. Otherwise it looks like you have to get involved with NSInvocation (http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/Reference/Reference.html). I have no idea if doing so would be more difficult than what I suggested above.

If you did want to try my suggestion, in animateIntVariable you'd have some code like:
id passInfo = [[AnimateInformation alloc] initWithVariable:aVariable andAnimateTo:aValue andSpeed:aSpeed andIncrement:anIncrement];
[self incrementInt:nil withInfo:passInfo];


then change incrementInt to:

- (void)incrementInt:(NSTimer *)aTimer withInfo:(id)passInfo{
NSLog(@"incrementInt invoked");
[userObject performSelector:userSelector withObject:passInfo];
}


then change draw to:

-(void) draw: (id) passInfo {
AnimationInformation *myInfo = passInfo;
/* do some stuff with myInfo
...
*/
}


-Lee

liptonlover
Sep 2, 2008, 04:27 PM
Thanks lee :) when I get to that problem I'll look more into your solutions. I'm thinking about possibly just eliminating increment and speed, or increment and variableToAnimate as that one seems pointless. Since the whole idea of this is an animation, some of it might be pointless. I'll find out, anyways.
Nate

Palad1
Sep 3, 2008, 11:24 AM
While we're talking scoping...

I picked up a very nasty habit in C# :

class oof{
int rab;
void zab(int rab)
{
this.tab=rab; // works a treat, the field is set to the value of the arg
}
}


My poor attempt at translating this in obj-C :

@interface oof{
int rab;
}
- (void) zab:(int) rab;
@end

@implementation oof
- (void) zab:(int) rab{
self.rab=rag; // get a warning here about assignment to self.
}
@end


Is there anyway to get away with it or should I start renaming what I call the private side of my args (I'm still thinking methods and not messages, I know...)

Thanks!

ps: bonus points, what's the proper obj-Cish way of failing in an init method?

- (id) initWithHighRateOfFailure:(NSString*) highlyCrashProneArg{

if(highlyCrashProneArg != nil){
// business as usual
}else{
// DANGER DANGER! CANNOT INIT!!
// What should I do? there are no exceptions?
[self release];
return nil; // ? should I do this then?
}


return self;
}