selector again

Discussion in 'Mac Programming' started by liptonlover, Sep 2, 2008.

  1. macrumors 6502a

    Joined:
    Mar 13, 2008
    #1
    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).

    Code:
    #import <Cocoa/Cocoa.h>
    #import "ESAnimation.h"
    
    @interface AppController : NSObject {
        IBOutlet id field;
    	ESAnimation *animate;
    }
    - (IBAction)button:(id)sender;
    - (void)draw;
    @end
    
    Code:
    #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
    
    Code:
    #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
    
    Code:
    #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
     
  2. Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    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.
     
  3. thread starter macrumors 6502a

    Joined:
    Mar 13, 2008
    #3
    Thanks! Problem solved.
    I thought using instance variables declared in the header would work... apparently not.
    Nate
     
  4. Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    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...
     
  5. macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #5
    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:
    Code:
    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/document...s/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:
    Code:
    id passInfo = [[AnimateInformation alloc] initWithVariable:aVariable andAnimateTo:aValue andSpeed:aSpeed andIncrement:anIncrement];
    [self incrementInt:nil withInfo:passInfo];
    
    then change incrementInt to:
    Code:
    - (void)incrementInt:(NSTimer *)aTimer withInfo:(id)passInfo{
    	NSLog(@"incrementInt invoked");
    	[userObject performSelector:userSelector withObject:passInfo];
    }
    
    then change draw to:
    Code:
    -(void) draw: (id) passInfo {
      AnimationInformation *myInfo = passInfo;
      /* do some stuff with myInfo
      ...
      */
    }
    
    -Lee
     
  6. thread starter macrumors 6502a

    Joined:
    Mar 13, 2008
    #6
    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
     
  7. macrumors 6502a

    Palad1

    Joined:
    Feb 24, 2004
    Location:
    London, UK
    #7
    While we're talking scoping...

    I picked up a very nasty habit in C# :
    Code:
    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 :
    Code:
    @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?
    Code:
    - (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;
    }
    
     

Share This Page