archibaldtuttle
archibaldtuttle

Reputation: 105

Converting self-releasing objects to ARC

OK, so Apple brought ARC to us, which is great. After refactoring my Application to ARC almost everything works fine and it is a lot easier now to develop and maintain.

There is just one problem I still can't figure out.

My job management program shows different detail information of proposals, orders and so on in their own windows. So I have a special class where WindowControllers gets allocated and initiated with initWithWindowNibName and then the window is shown with showWindow:

DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:@"ThePorposalWindow"];
[proposalWindowController showWindow:nil];

Before ARC the Instance of the WindowController did the release like shown in the documentation:

- (void)windowWillClose:(NSNotification *)notification
{
   [self autorelease];
}

But now with ARC this is not possible anymore and what makes it even worse, in my special class where the WindowController is allocated and initiated, the same windowController is released by ARC because there is no pointer to the windowController.

My idea was to copy the windowController into an mutuable array:

[proposalWindowArray addObject:proposalWindowController];
[[proposalWindowArray lastObject] showWindow:nil];

And in the windowControllers delegate method windowWillClose I post a notification to my special class:

- (void)windowWillClose:(NSNotification *)notification
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"ProposalWindowWillClose" object:[[self window] windowController] userInfo:nil];
}

In my special class I listen to the notification and remove the object from the array:

- (void) proposalWindowWasClosed: (NSNotification *) notification
{
    [proposalWindowArray removeObjectIdenticalTo:[notification object]];
}

It works, but I still do not believe that this is the correct way.

Does anybody has the same problem or a tip to make it better?

Upvotes: 10

Views: 1676

Answers (4)

Platkus
Platkus

Reputation: 1

I had this same issue when I switched to ARC. Your solution works, but you're making it too complicated. You can essentially do what you were doing before by having the window release itself when it closes, but in an ARC compatible manner.

The solution is to simply create a property of your class within the class itself. For your example, in DetailWindowController, you would add the following property:

@property (strong)  DetailWindowController      *theWindowController;

Then when you create the window with your code above, add one line like so:

DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:@"ThePorposalWindow"];
[preferenceController setTheWindowController:proposalWindowController];
[proposalWindowController showWindow:nil];

Then finally, to have ARC release the window when it is closed like you did with the autorelease pre-ARC, in the DetailWindowController class, simply do:

- (void)windowWillClose:(NSNotification *)notification
{
    // Let ARC tear this down and clean it up
    [self setTheWindowController:nil];
}

Upvotes: 0

hamstergene
hamstergene

Reputation: 24439

Without hacks, there is no elegant way to keep an object retained other than having a strong reference to it in some other object. For example, you could keep a static NSMutableArray/NSMutableSet, add your controller there, and remove it in windowsWillClose:. This will be shorter than posting a notification. To make this reusable, create a WindowControllerRegistry singleton with an array, where you add controllers like this one, and which will automatically listen to NSWindowWillCloseNotification and remove them from its array thus releasing ownership.

As a quick workaround, you could perform retain/autorelease calls from non-ARC file:

my_retain(self);
my_autorelease(self);

// ArcDisabled.mm
void my_retain(id obj) { [obj retain]; }
void my_autorelease(id obj) { [obj autorelease]; }

Upvotes: 0

Rob Napier
Rob Napier

Reputation: 299585

I'd probably use a delegate approach rather than notifications. Generally it is better to have an external object that keeps track of the open windows. Self-retaining objects, like your old system, break the basic points of object ownership and make it hard to find things (such as "give me a list of open windows"). Non-Singletons that are just "floating" out there often come back to bite you in your architecture (I've had to fix this often enough).

That said, sometimes self-ownership is at least convenient, and at worst not-the-end-of-the-world. So self-own. The only difference is that you need to do it explicitly rather than matching a leak and an over-release (which is what your old code was doing).

Create a private strong property. Assign self to it. That will create a retain loop that will keep you around until you set the property to nil.

Upvotes: 10

fishinear
fishinear

Reputation: 6346

I think your alternative approach should be correct, but I don't think you need the second notification. You should be able to do:

- (void)windowWillClose:(NSNotification *)notification
{
    [proposalWindowArray removeObjectIdenticalTo:self];
}

Assuming the "proposalWindowArray" is a static NSMutableArray.

Upvotes: 0

Related Questions