开发者

What's the idiomatic way to manage reference counts during Obj-C initializers?

开发者 https://www.devze.com 2023-03-05 01:08 出处:网络
I\'m learning Objective-C. In my first non-trivial project I\'ve run into a question about how to best handle resources passed to the initializer, compared to the default initializer. My class has a r

I'm learning Objective-C. In my first non-trivial project I've run into a question about how to best handle resources passed to the initializer, compared to the default initializer. My class has a retained resource, engine, which can be set manually after creation, or during initialization explicitly, or during initialization by default:

- (id)init {
    if ((self = [super init])) {
        id e = [[XorShiftEngine alloc] init];
        [self setEngine: e];
        [e release];
    }
    return self;
}

- (id)initWithEngine:(NSObject <RandEngine> *)e {
    if ((self = [super init]))
        [self setEngine: e];
    return self;
}

- (id)setEngine:(NSObject <RandEngine> *)newEngine {
    [newEngine retain];
    [engine release];
    engine = newEngine;
    // Some other stuff which needs to happen on changing the engine.
    return engine;
}

The default initializer, in particular, seems very ugly to me, interleaving code relevant to the self, then the member, the开发者_C百科n self again, then the member again, and naming the object solely to be able to release it later. It also violates the designated initializer idiom.

Is there a more idiomatic way to do this, without using autorelease pools?


Are you talking about e in -init? I don't see the problem there... you create an object, you use it, you release it. That's the usual and correct pattern for creating and using objects. If you're unhappy with that, though, you could instead have -init pass nil into -initWithEngine: and have -initWithEngine: create the engine if none is provided.

People don't usually talk about a "default initializer" so much as a "designated initializer." The designated initializer is the one that all the other initializers call -- it's often one that allows for the most customization. In this case, -initWithEngine: is your designated initializer.


- init
{
    self = [super init];
    if (self != nil) {
        // initialization here
    }
    return self;
}

If you have "cover initializers", they should follow the same pattern:

- initWithBob:(Bob*)aBob
{
   self = [self init];
   if ( self != nil ) {
       ... deal with aBob here ...
   }
   return self;
}

This is also a good example of why it is wise to avoid multiple initializers on a class. It can be a pain to keep track and makes subclassing more tedious and error prone. Better to have one initializer and multiple subclasses or have a single initializer and allow the instances to be configured after the fact.

I.e. instead of initWithBob:, have @property(retain) Bob* bob; that can be called after init as needed. Limiting initializers to required state is a good rule of thumb.


Assuming you are declaring a Car class, I'd do something like:

@interface Car:NSObject
+ car;
+ carWithEngine:(Engine*)anEngine;
- initWithEngine:(Engine*)anEngine;
@end

The implementations should be obvious; car creates a default engine and calls alloc/initWithEngine:/autorelease.

That gives you a single initializer, making sub-classing dead simple obvious.


- (id)init {
    id e = [[XorShiftEngine alloc] init];
    self = [self initWithEngine: e];
    [e release];
    return self;
}

// designated initializer
- (id)initWithEngine:(NSObject <RandEngine> *)e {
    self = [super init];
    if (self != nil) {
        engine = [e retain];
        // engine initialization stuff
    }
    return self;
}

- (id)setEngine:(NSObject <RandEngine> *)newEngine {
    [newEngine retain];
    [engine release];
    engine = newEngine;
    // Some other stuff which needs to happen on changing the engine.
    return engine;
}

You've done basically the right thing. The only thing I would change is directly assigning the variable and retaining it rather than calling the setter in the init method.

Apple recommends not calling property setters/getters in the init and dealloc methods (I'm assuming you've declared a retain property and are just overriding the setter).

If you have engine customization code that needs to happen the first time the engine is set in the initializer as well as whenever it is changed, then you should refactor this out into a separate method.

0

精彩评论

暂无评论...
验证码 换一张
取 消