NSNumber constants in Obj-C
Asked Answered
N

4

18

I want to make some NSNumber constants via the same style used for NSStrings in this topic. That is, I'm creating separate constants.h/.m files and importing them into classes that need to access them.

Neoplasticism answered 4/9, 2012 at 17:55 Comment(0)
S
18

The trouble with doing this is that there isn't such a thing as a compile-time constant NSNumber. Only NSString gets that distinction. NSNumbers are always created dynamically. You can fake it by using a function that runs at your program's startup to initialize the variables. Your options:

  1. Create a class with a +load method that performs the initialization.

  2. In the file with the constants, include a function with __attribute__((constructor)). So, for example:

    // Constants.m
    
    NSNumber *someGlobalNumber;
    
    __attribute__((constructor))
    static void InitGlobalNumber() {
        someGlobalNumber = [[NSNumber numberWithInteger:1] retain];
    }
    

But of course then you can't reliably use these numbers in any other functions which are run that early in the startup process. This usually isn't a problem, but is worth keeping in mind.

The other option, which I've seen crop up a few times, is to have a class with accessors for the numbers instead of giving raw access to the variables. It's a bit of a heavier design, but it also feels less voodooish, which has its charms.

Seth answered 4/9, 2012 at 18:8 Comment(4)
Best alternative answer so far.Inhospitable
I prefer __attribute__((constructor_)) over +load because constructor is both universal and very easy to search for across a whole codebase whereas searching for load typically yields about a zillion unrelated hits.Oquassa
@Seth does this solution still work when automatic reference counting (ARC) is in use and calling retain is not allowed?Teleutospore
@HairOfTheDog: ARC calls retain for you when assigning to strong variables, so yes, it should be fine AFAIK.Seth
P
16

Unfortunately you cannot currently generate NSNumber constants in the same way you can generate NSString constants. When you try to do you will get a compiler error

NSNumber * const kNumberConstant = @2; // This doesn't work.

However, you can use primitives instead.

NSInteger const kSomeIntValue = 10;
Pulverize answered 4/9, 2012 at 18:4 Comment(11)
Of course because NSString constants are put into static memory, but NSNumber constants are autoreleased instances. Apple developers who created this are ... not very clever.Swish
@Sulthan: NXConstantString was not created by Apple developers AFAIK. It was already part of OpenStep.Seth
@Seth Literal notation for NSString is fine. The new Obj-C additions (e.g. expression/number literals) are something a modern language should not have.Swish
@Swish The source for both the Objective-C runtime and LLVM are available. If you have a better solution, implement it and submit a bug. Keep in mind that your solution needs to be compatible with millions of lines of code and needs to produce executable code that is backwards compatible across multiple years of releases and across several architectures.Oquassa
@Oquassa You should read some books about code quality and what experienced programmers thing about new obj-c additions (e.g. teachers from Big Nerd Ranch). I don't want to say that something doesn't work (submit a bug), I want to say that the additions are stupid - even with a macro you can create better solutions doing the same but more readable.Swish
@Swish Um. Yeah. You might want to do a little more background research on bbum. My 2¢.Home
@Sulthan: You just heard what an experienced Cocoa programmer thinks about the new Objective-C additions, dude. bbum is very experienced and ridiculously knowledgeable. The Big Nerd Ranch guys are awesome, but they don't speak for all experienced Cocoa programmers. And even all the Big Nerd Ranch guys don't think additions are "stupid" (I'm actually not aware that any of them have said so, but I know Mark Dalrymple at least said — and I quote — "I really like the new Objective-C literal syntax").Seth
It is a bit of to each his own; for a casual app, I go all in on the syntax (dot, subscripting, etc...). For my team's app, we have consciously decided to eschew dot syntax and array/dictionary subscripting, but are all in on the literal syntax and @property declarations.Oquassa
@bbum:would you care to share your reasons for the eschewals? Consistency with existing codebase / Personal style preference are 2 possibilities I can think of, but interested if there's something else.Tempt
Personally, I'm on the fence, but lean to convenience when building relatively small, non performance sensitive, typical high level, app code. For large scale apps (like my team's), the ambiguity of how much stuff might happen for that . or that [...] expression combined with the need to delve into code one might not have seen makes us lean to expressions that indicate exactly what is happening.Oquassa
@bbum:thanks. I would guess that the bulk of the code most of us write falls into the 1st category, in which case, I'm sure you're right that convenience (and I'd add readability) should reign.Tempt
A
5

You can basically achieve close to what you want in three parts:

.h file:

extern NSNumber *MyFirstConstant;

.m file

NSNumber *MyFirstConstant;

AppDelegate.m

+(void)initialize
{
    MyFirstConstant = @5;
    ...
}

AppDelegate is guaranteed to run before any of your other code, and the initialize is the first method that would be called on AppDelegate, so you can essentially insure all your constants are setup for you before your app runs.

Anthracite answered 4/9, 2012 at 19:37 Comment(0)
E
4

update:

Years later, I just realized it is possible to create a NSNumber constant for integers... but it's a hack:

#define CONST_INT_NSNUMBER( x ) ((__bridge NSNumber * const)(void * const)(( x << 8 ) | 0x27))


NSNumber * const number = CONST_INT_NSNUMBER(123) ;

This works because certain integer NSNumbers are stored as tagged pointers.


original answer:

You can't do it.

NSNumber * const mynumber = @5.5;

gives:

Initializer element is not a compile-time constant

Implying the compiler has a special feature specifically for creating compile-time constant NSString objects, but not any other type of object.

You could do this, however:

.h:

extern NSNumber * kConstantNumber ;

.m:

NSNumber * kConstantNumber ;

@implementation NSNumber (InitializeConstants)

+(void)load
{
    kConstantNumber = @42;
    // ... and the rest ...
}

@end
Exhibitionist answered 4/9, 2012 at 18:6 Comment(2)
One would hope that the compiler would at least warn that you're assigning an NSNumber to an NSString, even if it allowed that first expression.Bit
haha.. It did give a warning, but I only saw the error because that's what I was looking for.Exhibitionist

© 2022 - 2024 — McMap. All rights reserved.