Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C with ARC: Custom setter does not retain

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;
  }
}
like image 351
fzwo Avatar asked Aug 14 '11 10:08

fzwo


2 Answers

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.

like image 128
BJ Homer Avatar answered Sep 22 '22 12:09

BJ Homer


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.

like image 27
Wolfgang Schreurs Avatar answered Sep 19 '22 12:09

Wolfgang Schreurs