Vladimir Gritsenko
Vladimir Gritsenko

Reputation: 1683

Is `init` a bad place to implement a factory in Objective-C?

I implemented the old init-as-a-factory pattern, but in one particular case (but not others!) I get a warning from the analyser regarding memory leaks. And indeed, looking at the Cocoa Memory Management Policy rules, it is alloc, not init, which can return +1-retain-count objects.

So it appears that:

  1. Releasing self and returning a new object from init is, strictly speaking, against the rules.
  2. Many places on the internet promote this technique, and because of the tandem nature of alloc/init this does work.
  3. The analyser sometimes complains about this, and sometimes doesn't.

So... have we been doing this wrong all along?

Upvotes: 3

Views: 900

Answers (3)

Gabriele Petronella
Gabriele Petronella

Reputation: 108169

Without knowing what is the code that is causing the analyzer's behavior it's hard to tell, but as a general rule, here's a couple of compiler-friendly ways to define init/factory methods.

Classic alloc/init

- (instancetype)initWithParameter:(id)parameter {
    if(self = [super init]) {
       _parameter = parameter; 
    }
    return self;
}

Usage

MyCustomClass * myInstance = [[MyCustomClass alloc] initWithParameter:foo];

This will produce an instance with a +1 retain count. Under ARC this will be automatically managed properly since it follows the NARC rule (New, Alloc, Retain, Copy). For the same reason, in pre-ARC environments it has to be explicitly released by the client.

Custom factory method

ARC

+ (instancetype)canIHazInstanceWithParameter:(id)parameter {
    return [[self alloc] initWithParameter:parameter]; // assuming -initWithParameter: defined
}

Pre-ARC

+ (instancetype)canIHazInstanceWithParameter:(id)parameter {
    return [[[self alloc] initWithParameter:parameter] autorelease]; // assuming -initWithParameter: defined
}

Usage

MyCustomClass * myInstance = [MyCustomClass canIHazInstanceWithParameter:foo];

Both in ARC and pre-ARC the method returns an autoreleased instance (this is clearly more explicit in the pre-ARC implementation), which doesn't have to be managed by the client.

Remarks

  • You may have noticed the instancetype keyword. That's a handy language extension introduced by Clang, that turns the compiler into a dear friend when implementing your own constructors/factory methods. I wrote an article on the subject, which may be relevant to you.

  • Whether factory methods are preferable to init methods is debatable. From a client perspective it does not make much difference under ARC, provided that you carefully follow the naming conventions, even though I personally tend to expose factory methods in the interface, while implementing custom init methods only internally (as I did in the examples above). It's more a matter of style than an actual practical concern.

Upvotes: 0

Bryan Chen
Bryan Chen

Reputation: 46608

you can implemented init like this, which should release self to balance the retain count from alloc call.

- (id)initWithSomething:(id)something
{
    [self release]; // don't need this line for ARC
    self = nil;
    return [[PrivateSubClass alloc] initWithSomething:something];
}

and it if very often to implement init as a factory method. e.g. NSArray, NSDictionary, NSString

Upvotes: 2

Morion
Morion

Reputation: 10860

As gaige said, it will be much more clearer if you post a piece of code rather than explanations.

Anyway, you can move your factory to the class method, so you will have no such problem at all. I mean something like this:

MyClass* instance = [MyClass instanceWithParameters:params];

@interface MyClass
+ (MyClass*) instanceWithParameters:(ParamType)params;
@end

Upvotes: 0

Related Questions