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

回眸只為那壹抹淺笑 提交于 2019-11-28 08:38:21

问题


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?


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/15118909/arc-weak-ivar-released-before-being-returned-when-building-for-release-not-de

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!