4

I have a class that creates an object lazily and stores it as a weak property. Other classes may request this object, but must obviously keep a strong reference to it to keep the object from being deallocated:

// .h
@interface ObjectManager
@property(nonatomic, weak, readonly) NSObject *theObject;
@end

// .m
@interface ObjectManager ()
@property(nonatomic, weak, readwrite) NSObject *theObject;
@end

@implementation ObjectManager
- (NSObject *)theObject
{
    if (!_theObject) {
        _theObject = [[NSObject alloc] init];
        // Perform further setup of _theObject...
    }
    return _theObject;
}
@end

When the scheme is Xcode is set to build for Debug, things work just fine - an object can call objectManagerInstance.theObject and get back theObject.

When the scheme is set to build for Release, theObject returns nil:

// Build for Debug:
NSObject *object = objectManagerInstance.theObject;
// object is now pointing to theObject.

// Build for Release:
NSObject *object = objectManagerInstance.theObject;
// object is now `nil`.

My guess is that the compiler is optimising my code by seeing that _theObject is not used further in the accessor method itself, so the weak variable is being set to nil before returning. It seems that I would have to create a strong reference before actually returning the variable, which I can only think to do using a block, but would be messy and I'd rather avoid it!

Is there some kind of keyword I can use with the return type to stop the ivar from being nilled so soon?

Stuart
  • 36,683
  • 19
  • 101
  • 139
  • 1
    Why is the property defined as `weak`? Since `ObjectManager` creates the object and should hang onto it so others can access it, it should be a `strong` property. – rmaddy Feb 27 '13 at 18:00
  • @rmaddy A sensible enough question :) The reason I chose to design it that way is that `ObjectManager` serves a group of unrelated objects (it is a singleton class), and `anObject` is not always needed. I am trying to keep to a lazy design pattern and not keep `anObject` around when it is not needed. A weak property seemed like a convenient solution until I encountered this issue. – Stuart Feb 27 '13 at 18:21
  • If you don't want to keep it around, then get rid of the property and ivar. Just have `theObject` create and return an object. If your goal is to keep it around once it has been lazy loaded then it must be kept in a `strong` ivar. – rmaddy Feb 27 '13 at 18:26
  • @rmaddy If `theObject` is required by 2 other objects simultaneously, it must be the same instance of `theObject`. Do you know how I can keep the lazy loading but also ensure the object is deallocated when not needed? A form of manual reference counting might be possible, but messy. I accept that what I am trying to do is not 'best practice' - after all `theObject` isn't actually _owned_ by anything. Resigning to this fact means I will lose out on a bit of optimisation in my code, which is a pain, but I might be able to live with it (begrudgingly). – Stuart Feb 27 '13 at 18:40

2 Answers2

8

Most likely, DEBUG builds cause the object to sit in the autorelease pool long enough to cause it to "work" whereas a RELEASE build causes the optimizer to do a bit more control flow analysis which subsequently eliminates the autorelease chatter.

Frankly, that the compiler isn't spewing a warning in the release build saying that the code can never work is a bug (please file it as you have a great, concise, example)!

You'll need to maintain a strong reference somewhere to the object until whatever needs a strong reference has an opportunity to take a reference.

I'm wondering if something like this might work:

- (NSObject *)theObject
{
    NSObject *strongObject;
    if (!_theObject) {
        strongObject = [[NSObject alloc] init];
        _theObject = strongObject;
        // Perform further setup of _theObject...
    } else {
        strongObject = _theObject;
    }
    return strongObject;
}

I.e. the above would be more akin to a factory method that returns an autoreleased object while also maintaining a weak reference internally. But the optimizer might be too clever by half and break the above, too.

Monolo
  • 18,205
  • 17
  • 69
  • 103
bbum
  • 162,346
  • 23
  • 271
  • 359
  • [“A method or function which returns a retainable object type but does not return a retained value must ensure that the object is still valid across the return boundary.”](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#unretained-return-values). If the optimizer breaks your solution, the optimizer is violating the ARC spec. – rob mayoff Feb 27 '13 at 18:33
  • Rob -- the optimizer is right; see section 6.1 of the ARC spec on precise lifetime semantics. :) http://clang.llvm.org/docs/AutomaticReferenceCounting.html#precise-lifetime-semantics – Mark Bernstein Feb 27 '13 at 18:37
  • @bbum Thanks for your answer - the suggested code does indeed work. I will file a bug report for the lack of compiler warning as recommended. – Stuart Feb 27 '13 at 18:56
  • I think I just duplicated [this question here](http://stackoverflow.com/questions/23960634/why-can-a-weak-member-be-nulled-in-an-instance-method-before-the-method-is-finis/23963826#23963826), but even stranger (at least stranger to me) was that the weak member ivar is nulled even before the instance method had returned. I basically had the same setup us you, but just before you return `_theObject` I log its value and found it was already nil. Is that strange to anyone else? – JefferyRPrice May 31 '14 at 02:08
4

You're being bitten by the optimizer.

Since _theObject is a weak reference, the system is free to get rid of it, and zero out your weak reference, whenever it's not retained. But it's not required to do it right away.

In your lazy instantiator, the newly-created object is never retained. The optimizer sees this, and says "Wow! I can zero this reference at any time! Why don't I do it...right now!" And before you know it, you're returning nil.

What you want to do is assign the lazily-instantiated object to a local variable, for an implicitly strong reference that lasts for the scope of the function. You also want to tell the compiler that you really do want the full scope, using the objc_precise_lifetime annotation.

For details from the standard, see this page.

Mark Bernstein
  • 2,090
  • 18
  • 23
  • Thanks. As you and @bbum both confirmed, using a local variable does keep the object around at least beyond the return statement (even without the `objc_precise_lifetime` annotation). I may use the annotation just in case, however. I'm going to mark @bbum's answer as correct (sorry!) since he pipped you to the post and provided a code example, but thanks for the useful link to the ARC spec. – Stuart Feb 27 '13 at 19:05