views:

67

answers:

1

Hi, I'm new to Objective C. I am trying to define Constants which have behavior associated with them. So far I have done -

@interface Direction : NSObject {
     NSString *displayName;
}

-(id)initWithDisplayName:(NSString *)aName;

-(id)left; // returns West when North

-(id)right; // return East when North

@end

@implementation Direction

    -(id)initWithDisplayName:(NSString *)aName
    {
        if(self = [super init])
        {
            displayName = aName;
        }   
        return self;
    }

    -(id)left {};
    -(id)right {};

@end

Direction* North = [Direction initWithDisplayName:@"North"]; // error - initializer element is not constant.

This approach results in the indicated error. Is there a better way of doing this in Objective C.

+4  A: 

Short version

Store a geometric representation of your direction (using a vector or a simple angle) and compute the direction names dynamically. To get left and right directions, copy and rotate your vector by ±½π or ±90°, creating a new direction and returning that. If you only have 90° turns, you can use a point { 0, 1 }, and switch the directions, optionally negating them as you do so. ({ 0, 1 } is North; { 1, 0 } is East, { -1, -0 } is West, { -0, -1 } is South, so if you use this approach you'll have to have some extra code to determine when to negate, and when not to.)

Long version

The issue is that variables outside of functions are initialized at compile time. Naturally, this means that you cannot call functions to initialize them. To do something like this, you need to do the following (although you shouldn't, see below):

// Direction.h

extern Direction *north;

// Direction.m

Direction *north = nil;

+ (void)initialize {
  north = [[Direction alloc] initWithDisplayName:@"North"];
}

This is, however, an almost entirely terrible way of going about things. What you want to do is as follows:

// Direction.h


@interface Direction (ConstantDirections)

+ (Direction *)north;
// etc...

@end

// Direction.m

+ (Direction *)north {
  static Direction *northDirection = nil;
  if (!northDirection) {
    // assume that ConstantDirection is a subclass of
    // Direction that prevents releases etc.
    northDirection = [[ConstantDirection alloc] initWithDisplayName:@"North"];
  }
  return northDirection.
}

This will prevent other users of the direction from doing evil things (mostly).

PS: North is a really weird word, especially noticeable when you type it a lot. :P

Edit: Some clarification on the use of cardinal directions as separate objects.

In practice, your direction class will probably look something like this:

@interface Direction : NSObject <NSCopying,NSCoding> {
  NSString *displayName;

  NSPoint offset;
  // XOR
  NSInteger northOffset;
  NSInteger eastOffset;
  // XOR
  CGFloat theta;
}

@property (copy) NSString *displayName;
@property (readonly) NSPoint offset;

+ (Direction *)north;
+ (Direction *)east;
+ (Direction *)west;
+ (Direction *)south;

- (id)initWithOffset:(NSPoint)offset;

@end

For the purpose of this discussion, we'll assume the first.

In this case, your Direction.m file would contain, among other things, the following.

@implementation Direction 

+ (Direction *)north {
  static Direction *northDirection = nil;
  if (!northDirection) {
    // assume that ConstantDirection is a subclass of
    // Direction that prevents releases etc.
    northDirection = [[ConstantDirection alloc] initWithOffset:NSMakePoint(0,DIRECTION_LARGE_OFFSET)];
    northDirection.displayName = @"North";
  }
  return northDirection.
}

@end
Williham Totland
The idea here was also for each Direction to encapsulate corresponding behaviour ie. left, right (example above). How is that possible if each Direction is an instance of ConstantDirection.
Akshay
Further subclasses of `ConstantDirection` (or just `Direction` directly.). `North : Direction`. Although, again, this is an almost entirely terrible way of going about it, updating answer.
Williham Totland
initialise northDirection to nil, not NULL. /pedantic.
JeremyP
@JeremyP: Hmm. That *does* seem overly pedantic. Personally I always use `NULL` for these kinds of things specifically; not really sure why. Must have seen it in some Apple™ Brand Example Code (*snort*) somewhere. Also, not really. What you want here isn't `(struct objc_class *)NULL` (`nil`); it's really `(Direction *)NULL`. I feel the `NULL` is appropriate.
Williham Totland
Disagree. Direction is a reference to an Objective-C object and as such it is appropriate to use nil here. I know it actually makes no difference (notwithstanding long tedious discussions on the Apple lists about whether NULL really is 0), but I prefer the Objective-C way.
JeremyP
@JeremyP: I'm not saying that the `nil` is *in*appropriate, I'm just saying that, in this particular case, `NULL` is at least equally appropriate. But, being the sucker for peer pressure that I am, I will give in, and edit my post accordingly. :P
Williham Totland