views:

108

answers:

2

What's the best way to handle memory management with nested factory methods, such as in the following example?

@interface MyClass : NSObject {
    int _arg;
}

+ (MyClass *) SpecialCase1;
+ (MyClass *) SpecialCase2;
+ (MyClass *) myClassWithArg:(int)arg;
- (id) initWithArg:(int)arg;

@property (nonatomic, assign) int arg;

@end

@implementation MyClass

@synthesize arg = _arg;

+ (MyClass *) SpecialCase1
{
    return [MyClass myClassWithArg:1];
}

+ (MyClass *) SpecialCase2
{
    return [MyClass myClassWithArg:2];
}

+ (MyClass *) myClassWithArg:(int)arg
{
    MyClass *instance = [[[MyClass alloc] initWithArg:arg] autorelease];

    return instance;
}

- (id) initWithArg:(int)arg
{
    self = [super init];
    if (nil != self) {
        self.arg = arg;
    }

    return self;
}

@end

The problem here (I think) is that the autorelease pool is flushed before the SpecialCaseN methods return to their callers [Edit: apparently not - see comments below]. Hence, the ultimate caller of SpecialCaseN can't rely on the result having been retained. (I get "[MyClass copyWithZone:]: unrecognized selector sent to instance 0x100110250" on trying to assign the result of [MyClass SpecialCase1] to a property on another object.)

The reason for wanting the SpecialCaseN factory methods is that in my actual project, there are multiple parameters required to initialize the instance and I have a pre-defined list of "model" instances that I'd like to be able to create easily.

I'm sure there's a better approach than this.

[Edit: @interface added per request.]

+2  A: 

Why do you think the autorelease pool is being flushed?

The autorelease pool is not flushed in cocoa-touch unless either you flush it, or control is returned back to the event loop.

From the iPhone Memory Management Guide

The Application Kit automatically creates a pool at the beginning of an event cycle (or event-loop iteration), such as a mouse down event, and releases it at the end, so your code normally does not have to worry about them.

Brandon Bodnár
From reading this answer, specifically:"No. You can only count on the object being around until you return from whichever method or function you're in. Typically, autorelease pools are flushed when control returns back to your run loop.If you want an object instance to survive beyond the current method you must take ownership of it, by calling 'retain'. You are then also responsible for 'releasing' the instance when you no longer need it."http://stackoverflow.com/questions/2017793/question-about-factory-method-object-lifetimes-in-objective-c-cocoa-to-retain-or/2017844#2017844
StephenT
Yeah, which means they are not flushed until the method that was called by the event loop returns. If you want to keep something after that you have to retain it, either by sending a retain message or putting it in a property with either the retain or copy attribute. You do that with the result of SpecialCaseN. My point is that the autoreleased object is still active after it returns from SpecialCaseN
Brandon Bodnár
This is why many people warn against rephrasing the memory management guidelines. It gives people weird ideas like that the top-level autorelease pool is magically flushed when a method returns. People should read the Memory Management Guide and go by what it says.
Chuck
I see what you mean. So, at face value there's nothing obviously flawed with the approach above?
StephenT
@Brandon - "If you want to keep something after that you have to retain it, either by sending a retain message or putting it in a property with either the retain or copy attribute. You do that with the result of SpecialCaseN." So that's precisely what I do with the result of SpecialCaseN (put it in a property with the copy attribute), but that's when it blows up with [MyClass copyWithZone:] being an unrecognized selector.
StephenT
+1  A: 

The error describes the problem exactly: copyWithZone is called and it's not implemented in MyClass. Check out the attributes or the implementation of the property it's assigned to (see if it's copying). Also, copyWithZone will be called if the instance of MyClass is used as a key to an NSDictionary. Here's some info on implementing copyWithZone.

dbarker
It's common to assume that copyWithZone: is inherited form NSObject but it is not. When you make a NSObject subclass, you usually have to implement it directly.
TechZen
Yep - that was it. I had indeed assumed I got copyWithZone: for free from NSObject. Thanks!
StephenT