3

I use the following as a getter for a property in one of my classes:

- (NSString *)version
{
    if (_version == nil) {
        _version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
    }
    return _version;
}

This works well. However, when I try the same for an int property I obviously get an error since int are never nil. What is the best way around this?

- (int)numberOfDays
{
    if (_numberOfDays == nil) {
        // relatively memory intense calculation that works out numberOfDays:
        _numberOfDays = X;
    }    
    return _numberOfDays;
}
Charlie S
  • 4,366
  • 6
  • 59
  • 97

5 Answers5

2

Firstly, using int is not recommended Objective-C if possible. If you need to use a primitive integer type, you should use NSInteger. The size of NSInteger is determined at compile time based on the architecture(s) being built for. int is a static size that will not widen for different architectures. It's OK to use it, just be aware.

Using NSInteger, you still face the same problem, it can't be nil. You should therefore make your property an NSNumber which you can init with the result of your computation with [NSNumber numberWithInteger:anInteger];. That way, you can keep you nil check on your property and only do the computation once to create your NSNumber.

Patrick Goley
  • 5,397
  • 22
  • 42
  • Why should not be `int` used in Objective-C? –  Dec 18 '13 at 20:19
  • 2
    I just explained that. see this answer http://stackoverflow.com/questions/4445173/when-to-use-nsinteger-vs-int – Patrick Goley Dec 18 '13 at 20:20
  • "It has a different size on different architectures" is not an explanation, since that's true for `int` as well. Also, the answer you linked to is disagreed with by quite a few people. –  Dec 18 '13 at 20:26
  • 1
    @H2CO3, just as a note: `int` does not change size on any of the platforms we're talking about. It's always 32-bit. It *could* be different, but it never is within the Apple world, and it almost certainly never will be. – Rob Napier Dec 18 '13 at 20:35
  • 2
    @RobNapier iOS is not the only OS in the world ;-) Also, this: "int is a static size that will not optimize for different architectures." - is specifically incorrect. If we are splitting hair, let's do it correctly: in C, `int` is supposed/advised to be the fastest integer type. So if we are doing such optimization (***why?***), then it's even better to use. Now of course, ranges may be a real problem. And indeed, if there's an application that relies on an integer type being as wide as possible, then that should be `NSInteger` -- but else, that typedef hasn't any other real advantage. –  Dec 18 '13 at 20:38
  • Thank you all for your clarifications, I have edited my answer to be less emphatic on not using int, since that isn't the focus of the question. – Patrick Goley Dec 18 '13 at 22:47
1

Use GCD.

static dispatch_once_t tok;
dispatch_once(&tok, ^{ memory_intensive_computation(); });

No, don't use GCD, I missed the point. In an instance method, you want to tie information to each instance, so using a static dispatch token is not appropriate. Maybe you should just stick with the "boolean flag as instance variable" approach.

Alternatively, you can initialize the int to a value which is known to be out of its valid range (for example, I suppose that numberOfDays can never be negative) and use that as a condition for performing the calculation.

  • 2
    `- (int)numberOfDays` is an *instance method*, and each instance has its own `_version`. So you cannot use a static dispatch_once_t. And using dispatch_once_t predicate as a member variable seems not to be allowed, compare http://stackoverflow.com/questions/13856037/can-i-declare-dispatch-once-t-predicate-as-a-member-variable-instead-of-static. – Martin R Dec 18 '13 at 20:15
  • @MartinR Huh, I missed that. Surely you're right, let me think about this a bit more. –  Dec 18 '13 at 20:18
  • @MartinR: Yowza, I didn't know about the dynamic storage restriction either. What a bother. I wonder what that's all about. – Chuck Dec 18 '13 at 20:19
  • It's because they use the actual memory location as the "I've done this" token. It needs to be something that can never change. – Rob Napier Dec 18 '13 at 20:20
  • @RobNapier: Are you sure? Why wouldn't they store it in the actual token? Based on the comment in the header, I suspect the docs are slightly wrong on this — what it actually means is that the memory for the `dispatch_once_t` needs to be zeroed before `dispatch_once` is called, and only static and global variables are initialized that way by default in C. I'd like to hear a comment from Apple on the matter. – Chuck Dec 18 '13 at 20:22
  • @RobNapier I've read the answer Martin R linked to, and that doesn't actually confirm either of the hypotheses. So we can only speculate. (But even from the speculations, the author of the answer deduced that it's not the address of the token that matters but its value.) –  Dec 18 '13 at 20:24
  • 2
    As far as I know, Greg Parker is from Apple, and he posted this answer http://stackoverflow.com/a/19845164/1187415 to the above thread. – Martin R Dec 18 '13 at 20:25
  • @MartinR Oh, that's nice. I didn't know we had an authoritative answer to the question. –  Dec 18 '13 at 20:28
  • @MartinR: Ah, thanks. How weird. I still can't imagine how the fact that the memory was non-zero before the object was initialized would ever matter to a call in an instance method, but Greg is much smarter than I am, so I'll call that settled. – Chuck Dec 18 '13 at 20:32
1

Use a NSNumber to store the int value.

- (int)numberOfDays
{
    if (_numberOfDays == nil) {
        // relatively memory intense calculation that works out numberOfDays:
        _numberOfDays = @(X);
    }    
    return [_numberOfDays intValue];
}
Hackmodford
  • 3,901
  • 4
  • 35
  • 78
1

Add another boolean instance variable _numberOfDaysCalculated. A thread-safe version would be

- (int)numberOfDays
{
    @synchronized(self) {
        if (!_numberOfDaysCalculated) {
            // relatively memory intense calculation that works out numberOfDays:
            _numberOfDays = X;
            _numberOfDaysCalculated = YES;
        }
    }    
    return _numberOfDays;
}

Alternatively, if there is some "invalid" value of the property, you can use that as a "not yet computed" marker. For example, if the computed value of numberOfDays has to be non-negative, you could initialize _numberOfDays = -1 in the init method, and then test for if (_numberOfDays == -1) in the lazy getter method.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Ugh, so you added the "sentinel value out of range" method as well, I see :) –  Dec 18 '13 at 20:35
0

I would initialize the _numberOfDays in the -init with NSNotFound and test for that in the getter.

GeorgS
  • 1
  • 1