Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 954
Stuart Avatar asked Dec 16 '22 13:12

Stuart


1 Answers

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.

like image 155
bbum Avatar answered Dec 18 '22 01:12

bbum