ARC weak ivar released before being returned - when building for release, not debug
Asked Answered
C

2

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?

Candlelight answered 27/2, 2013 at 17:53 Comment(4)
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.Puce
@Puce 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.Candlelight
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.Puce
@Puce 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).Candlelight
P
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.

Proximity answered 27/2, 2013 at 18:28 Comment(4)
“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.”. If the optimizer breaks your solution, the optimizer is violating the ARC spec.Calcutta
Rob -- the optimizer is right; see section 6.1 of the ARC spec on precise lifetime semantics. :) clang.llvm.org/docs/…Translucid
@Proximity Thanks for your answer - the suggested code does indeed work. I will file a bug report for the lack of compiler warning as recommended.Candlelight
I think I just duplicated this question here, 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?Unspent
T
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.

Translucid answered 27/2, 2013 at 18:35 Comment(1)
Thanks. As you and @Proximity 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.Candlelight

© 2022 - 2024 — McMap. All rights reserved.