11

NOTE: This was due to a bug in some XCode beta versions that has long been fixed. This Question and answer will probably not help you if you have problems with ARC.


I am migrating my project from manual reference counting to ARC, and have stumbled upon a problem: How can I ensure that a custom setter for a retain property actually retains?

In myClass.h, I've declared a property: @property (retain) NSDate *date. It doesn't matter whether I manually set a __strong ivar or have it autogenerated.

In the implementation, I have of course @synthesize date, and implemented a custom setter (or just download the demo Xcode project):

- (void)setDate:(NSDate *)newDate
{
  if (allowedToSetNewDate)
  {
    date = newDate;
  }
}

This does not seem to retain the date, and gives me message sent to deallocated instance when newName is (auto-) released where it came from, when trying to access myClass.date later (provided Zombie is enabled; otherwise, it just crashes silently).

Changing the setter to use date = [newDate copy] works around the error, but isn't really what I want. Deleting the custom setter also works, but is obviously not desireable.

What am I missing here? How can I ensure that a custom setter for a retain property actually retains in an ARC environment? This seems such a basic and common task that I think I'm overlooking something very obvious.

(NOTE: This does not fall under the terms of any Apple NDA, as ARC is publically released as part of LLVM)

EDIT: I've created a small Xcode project to demo the issue and uploaded it to github. Feel free to download it and play around. I'm at my wit's end (though my wit is not at its best today, admittedly).

EDIT: For this sample project, this problem is solved (see accepted answer). Unfortunately, in the bigger project, which I am not at liberty to share, the problem persists. As a workaround, I've added duplicate strong properties with synthesized setters (ivars don't work). The new custom setter now looks like this:

- (void)setDate:(NSDate *)newDate
{
  if (allowedToSetNewDate)
  {
    self.date_arcretain = newDate; //this property is only there as a workaround. ARC properly retains it, but only if the setter is synthesized
    date = newDate;
  }
}
fzwo
  • 9,842
  • 3
  • 37
  • 57
  • This may help a little, pertaining the use of (nonatomic, retain) that you should perhaps be calling on your NSString: http://stackoverflow.com/questions/1380338/objective-c-101-retain-vs-assign-nsstring – Luke Aug 14 '11 at 10:37
  • @Luke, I don't see what that has to do with this question. I do understand how retain counting works, and what property declarations mean - as I have written in the question, I am migrating from a project (with manual counting, not GC - will add this) to ARC. EDIT: I see now, you mean that strings should be `copy`d. I'll update my example, because this does not only concern strings. – fzwo Aug 14 '11 at 10:48
  • Not quite sure, but I think you have to qualify the `newDate` parameter as `__strong`. – omz Aug 14 '11 at 12:34
  • @omz Thanks, I'd thought about that, but it doesn't seem to work (or I'm doing it wrong, which is entirely possible). I'll be uploading a very small project that demonstrates this in a few minutes, and I'd invite everyone to play around and see if it happens for them and how they can solve it. Frankly, I don't know what I should do different, seeing how I can't retain manually. – fzwo Aug 14 '11 at 12:40
  • Or maybe you need to declare the instance variable as `__strong`. Frankly, I haven't really worked with ARC so far, so this is mostly guessing from what I understood of the documentation. – omz Aug 14 '11 at 12:46
  • @omz I've tried that too, of course. With or without backing ivar doesn't make a difference, and explicitly declaring the ivar to be strong doesn't either (as well it should, `__strong` being the default). – fzwo Aug 14 '11 at 12:50
  • Yeah, `__strong` being the default is a good point, hadn't thought about that. I've starred the question and I hope you find a solution. – omz Aug 14 '11 at 13:00
  • Very interesting question! I will watch it :D – Kheldar Aug 14 '11 at 13:20
  • To all commenters: It essentially boils down to a bug (in LLVM, I think). Read the edit at the bottom of the accepted answer, there's the recipe for success. – fzwo Aug 17 '11 at 14:14

2 Answers2

12

This looks like a bug to me; your code should be fine. If you haven't yet done so, file a bug at http:// bugreport.apple.com, and attach your sample project.

Edit: On further examination of your sample project, this is not a bug.

The overreleased object in your sample project is not the NSDate instance. You can comment out the tc.date = now call in your sample project entirely, and you'll still see the same crash. In fact, you can take out the NSDate stuff entirely. The over-released object is actually the TestVC object itself.

Here's what's happening.

In iOS 4.0, UIWindow got a rootViewController property. Where previously you would just call [self.window addSubview:myRootcontroller.view] upon launching the app, this change now meant that the window would actually have a reference to the root view controller. This is important for the sake of passing on rotation notifications, etc. In the past, I believe UIWindow would automatically try to set the rootViewController when the first subview was added (if it was not already set), but in your sample project that is clearly not happening. That may be due to the way you're creating the view, or may be due to a change in iOS 5.0. Either way, it wasn't ever documented behavior, so you can't rely on it happening.

In most cases, your app delegate will have an ivar pointing to the root view controller. It's not strictly required, but it's usually what happens. However, in the sample project you presented, the view controller is not owned by the app delegate. Nor did you set it as the window's root view controller. As a result, at the end of the -application:didFinishLaunchingWithOptions: method, there is nothing left with a strong reference to the view controller. Your app delegate isn't holding on to it, and the window itself isn't holding on to it. As such, ARC treats it as a local variable (which it is), and releases it at the end of the method.

Of course, that doesn't the change the fact that your UIButton still has an action method targeted at your controller. But as noted in the documentation, -addTarget:action:forControlEvents: does not retain the target. So the UIButton has a dangling reference to your view controller, which has now been deallocated since nobody has a strong reference to it. Hence the crash.

The fix for this is to change this line in your app delegate:

[self.window addSubview:tc.view];

to this:

self.window.rootViewController = tc;

With that single change, everything now works just fine.

Edit: Also make sure the "Precheck code for ARC migration" setting is not on, as this would cause the compiler to treat the code as manually managed, and this would not insert the proper retain/release calls.

BJ Homer
  • 48,806
  • 11
  • 116
  • 129
  • Great Job, thank you very much! I'll have to see if this is also the case with my bigger project, but it might well be. I'm marking your answer as correct. If the problem turns out to persist, I'll get back to you. BTW, you caught something even an apple dev didn't :) – fzwo Aug 14 '11 at 18:30
  • Unfortunately, this is not the solution. Despite your very nice explanation, the ViewController itself does not get deallocated. I've updated the sample project, and added an NSLog to clearly show this. I've also set the rootViewController explicitly, just because it's good design (thanks for the pointer!). I'm removing the acceptance, and will report this as a bug. – fzwo Aug 15 '11 at 10:02
  • 1
    Your update to the project changed more than adding `self.rootViewController = tc;` — you also changed the "Migrate code from MRR to ARC" build setting from `Do not run any phase of the migration` to `PRECHECK code for ARC incompatibilities`. I've seen other reports of ARC misbehaving when that build setting was on, and indeed: turn off the precheck and once again the sample project works just fine. That precheck issue is definitely one worth filing a bug over, incidentally. – BJ Homer Aug 15 '11 at 12:52
  • Thank you again, BJ Homer. I'd forgotten I'd played around with that setting to see if it made a difference. If you would be so nice as to add that piece of information (disable precheck), I'll accept your answer again. – fzwo Aug 15 '11 at 13:55
  • The problem is now solved. Thank you very much! When setting the "Precheck..." build setting, make sure that it is set for every target, not just the project. I grew a few gray hairs trying to figure out why it worked for one project and not for another. – fzwo Aug 17 '11 at 14:13
2

I fixed it on my Mac with your project. The error is in your AppDelegate, not in your date property. Make the view controller a retainable property in AppDelegate. Currently your view controller is autoreleased, including all it's properties.

Wolfgang Schreurs
  • 11,779
  • 7
  • 51
  • 92
  • This is not the case in my simulator (newest version) or device (newest firmware). Neither is the VC autoreleased, nor does explicitly setting it fix the problem. I've updated the sample project, and it still crashes for me. – fzwo Aug 15 '11 at 12:29