“Should I use #define or a constant variable in Objective-C?”

Well, at least you are asking the question. Most people just choose whatever they learned first or are most comfortable with. Turns out, there is a reason for using one versus the other.

Let me start out by saying that this isn’t very good:

#define FUN_FACTOR 1.4

The reason it isn’t very good is because someone will inevitably write the line of code:

float howMuchFunIsThis = FUN_FACTOR / 2;

And then someone will inevitably decide that there is just too much fun in the world:

#define FUN_FACTOR 1

Which will make this ZERO fun at all:

float howMuchFunIsThis = FUN_FACTOR / 2;

Constant variables

This problem could be solved in a number of ways, but the safest way is to declare FUN_FACTOR as a const float, ensuring that it is always treated as a float and never as an integer. The important bit here is that a variable gives us type safety whereas a #define does not.

const float FUN_FACTOR = 1;

Now, as an aside, FUN_FACTOR is a crappy variable name. It’d be a lot better if we understood where FUN_FACTOR was supposed to be used and made sure that there weren’t any other FUN_FACTORs declared in other files.

The convention in Objective-C is to effectively namespace your constants to the file or class they belong to and usually to the property they are used for. For example, let’s say I have a class like so:

@interface BNREarthquake : NSObject

@property (nonatomic, assign) float strength; // in Richter

@end

Now, I’d like to be able to assign a value to the strength of an earthquake by using some common Richter scale value equivalents. So, I would declare these values like so:

const float BNREarthquakeStrengthHandGrenade = 0.2;
const float BNREarthquakeStrengthChernobyl = 3.87;
const float BNREarthquakeStrengthTsarBomba = 8.35;

Notice the form – ClassPropertyValue – which now indicates to everyone where these constants came from and what they intend to do. The benefit of this approach, besides preventing namespace collisions, is that I don’t ever have to look up all of the possible strength constants. I simply start typing in the name of the class and the property I’m trying to access, and Xcode’s code completion will list out the possibilities.

Class Methods

Another approach is to use class methods to return constants:

+ (float)grenadeStrength;

I don’t like this approach because it adds the extra overhead of a method call and extra typing for absolutely zero benefit. However, this approach is useful for values that aren’t constant but are related to the class. Let’s say there was a default strength for all BNREarthquake instances that we’d like to be able to customize at runtime. We then might declare a set of accessors for the BNREarthquake class:

+ (void)setDefaultStrength:(float)strength;
+ (float)defaultStrength;

#define

Now, don’t worry about how #define feels about getting kicked out of the constant club, because it still has a lot of uses outside of declaring constant variables.

Define is great for replacing what would otherwise be a copy-paste job. For a contrived example, I could have code like this:

[dict setObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObjects:foo, bar], @"k1", nil] forKey:@"start"];

[dict setObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObjects:bar, baz], @"k1", nil] forKey:@"middle"];

// and so on

Since there isn’t really much changing between these lines, I could define a little helper macro to help me with my job:

#define KEY_INSERT(A, B, K) [dict setObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObjects:A, B], @"k1", nil] forKey:K]

KEY_INSERT(foo, bar, @"start");
KEY_INSERT(bar, baz, @"middle");
// and so on

#undef KEY_INSERT

This helps me with typing, which helps me with errors. Another thing define does well is aliasing. Let’s say you have an application that logs out a ton of data, but you only interested in certain parts of it at a time. I like to make a special logger macro for each part of an application that I can turn on and off easily:

#ifdef PDF_LOG
    #define PDFLog(...) NSLog(__VA_ARGS__)
#else
    #define PDFLog(...) do {} while(0)
#endif

Now, I can leave in all my logs statements in and turn them off and on when I want without adding any extra code to execute in the application.

The problem with define is that it can be a slippery slope. Define should not be used where you can use a C function. For example, you might be tempted to write the following:

#define AbsoluteValue(x) (x >= 0 ? x : -x)

Your rationale in this case is, “This is just a simple check. If I put it in a function, I waste the overhead of calling another function.” Doesn’t work out that way in practice, though. First, this code can cause subtle issues like:

int b = 4;
int a = AbsoluteValue(b++);

Which evaluates to:

int b = 4;
int a = (b++ >= 0 ? b++ : -b++);

Which makes a equal to 5 and b equal to 6. Yikes. Also, the compiler is purty smart, so when it sees a function like,

int AbsoluteValue(int x)
{
    return (x >= 0 ? x : -x);
}

it’ll inline the function, which means the function doesn’t actually get called, but its code gets executed.

And finally, the last caveat I’d add to using #define is don’t get too complex. Let’s say I’m putting three or four similar buttons onto the screen:

CGRect r = CGRectMake(20, 20, 80, 40);

UIButton *button1 = [UIButton buttonWithType:UIButtonTypeCustom];
[button1 addTarget:self
            action:@selector(action1:)
  forControlEvents:UIControlEventTouchUpInside];
[button1 setImage:[UIImage imageNamed:@"button1.png"]
         forState:UIControlStateNormal];
[button1 setFrame:r];
[[self view] addSubview:button1];

r.origin.y += 60;

UIButton *button2 = ...;
// and so on.

This is a copy-paste job with a few changes, so you could use a define, but it is getting a little complex. Instead, try using a block:

__block CGRect r = CGRectMake(20, 20, 80, 40);
void (^addButton)(SEL, NSString *) = ^(SEL action, NSString *imageName) {
    UIButton *b = ...;
    ...
    r.origin.y += 60;
};

addButton(@selector(action1:), @"button1");
addButton(@selector(action2:), @"button2");


{{cta('80478c67-47c9-488c-b888-4ec1815206d1','justifycenter')}} 
Joe Conway

Founder at Stable Kernel

Leave a Reply

Your email address will not be published. Required fields are marked *