6

I have a auto-synthesized readonly & weak property:

@property (nonatomic, readonly, weak) KTWindowController* windowController;

I assign the synthesized ivar and then add it to an array:

_windowController = [KTWindowController controller];
[self addSubController:_windowController];

This works fine in Debug builds. But I got a report that in release (ad hoc) builds this will immediately nil the _windowController and then it tries to add nil to the array, crashing the app.

What specific setting (optimization level?) in a release (ad hoc) build changes this behavior compared to Debug builds?

It strikes me as odd that this behavior would change from Debug to Release builds. But I was able to reproduce this behavior, and it actually makes sense - just not when it's not coherent with what happens in debug builds.

The suggested workaround:

KTWindowController* windowController = [KTWindowController controller];
[self addSubController:windowController];
_windowController = windowController;

Other than using a local variable as seen above, what workaround would you recommend in cases like these?

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
  • Maybe this is a silly question, but why are you assigning directly to the ivar instead of using `self.windowController = [KTWindowController controller]`? – bdesham Apr 02 '13 at 17:01
  • The question **is** silly: the property is readonly. ;) – CodeSmile Apr 02 '13 at 17:02
  • I eventually found this: [ARC weak ivar released before being returned - when building for release, not debug](http://stackoverflow.com/questions/15118909/arc-weak-ivar-released-before-being-returned-when-building-for-release-not-de) – CodeSmile Apr 02 '13 at 17:22
  • It seems to me that your property is useless, since it goes poof immediately. Why even have it? – Hot Licks Apr 02 '13 at 17:37

2 Answers2

1

When you declare a property as weak, you are promising that some other object takes care of ownership. That's what weak means. When you violate this, bad things happen. So, for example, when you write:

KTWindowController* windowController = [KTWindowController controller];
[self addSubController:windowController];
_windowController = windowController;

you're meeting your obligations: the strong temporary variable windowController handles ownership within this method, and then windowController subController handles ownership afterward.

When you wrote

 _windowController = [KTWindowController controller];

you weren't doing what you promised to do. _windowController is weak, so someone else is managing the lifetime. But -- look! No one is managing the lifetime! So we're free to get rid of the weak variable whenever we want to. The optimizer looks at this in your release build and says,

Hey! This guy says he doesn't care if this window controller lives or dies, as long as nobody else cares. Some people! But that's none of my business. But look at this: since he doesn't care, I don't have to make it at all! Or, at any rate, I can get rid of it the instant I make it.

The compiler is just doing what you said you wanted.

Are you sure you want this to be a weak property? Whenever I see this in my code, I make a point of checking that the property really should be weak; often, I want a strong property anyway.

Mark Bernstein
  • 2,090
  • 18
  • 23
0

I found out that changing Optimization Level from None (-O0) to Fast (-O, O1) introduces this issue.

As mentioned here, this behavior is to be expected. It really ought to be a compiler warning.

I ended up merging the extra line in the workaround by moving the assignments to one line, this works just as well:

// extra local var to prevent ARC from nil'ing the weak var right away in release
KTWindowController* windowController = _windowController = 
                                                 [KTWindowController controller];
[self addSubController:windowController];
Community
  • 1
  • 1
CodeSmile
  • 64,284
  • 20
  • 132
  • 217
  • So what is the purpose of having the property, if it's unusable? – Hot Licks Apr 02 '13 at 17:38
  • 1
    It shouldn't be a compiler warning, you are saying that you want to assign an object without incrementing the retain count because you just need it while it's being used somewhere else and this is a perfectly fine statement, you just need to be aware of what you are doing. What you are doing is referencing it on a strong variable, so it'll retain until this variable is disposed. – Marco Pompei Apr 02 '13 at 17:43
  • @HotLicks I agree, it would be easier to use a `readwrite strong` property, but maybe he really needs it to be this way. – Marco Pompei Apr 02 '13 at 17:46
  • a property that is set once and then accessed by other classes doesn't make it unusable. Readwrite would allow the other classes to replace or nil the object, which would have undesirable consequences so it's just good practice to prevent that. It could be an accessor method but those are more verbose to write and use. – CodeSmile Apr 02 '13 at 20:24
  • The solution seems like it has potential to break - could ARC still release the return value of `controller` after it is assigned to `_windowController` but before its assignment to `windowController`? – Carl Veazey Apr 02 '13 at 21:17
  • Could it? I can't say for sure but it did work in debug and release builds. I think it'll work because the intention here is not ambiguous to the compiler - at the end there's an unavoidable assignment to a strong local variable. – CodeSmile Apr 03 '13 at 00:09