Search code examples
iosobjective-cautomatic-ref-countingweak-referencesrelease-builds

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


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?


Solution

  • 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.