Initializing a property, dot notation
Asked Answered
S

1

22

Is it a bad idea to use the dot notation to initialize retain properties to nil in my init methods?

With any ordinary property like this:

@property (nonatomic, retain) id foo;

Say in my init method I set self.foo = nil. The synthesized method first releases or autoreleases foo (not exactly sure of the underlying impementation). Is foo guaranted to be nil before the first setter or getter call? Or would it point to random garbage unless I explicitly set foo = nil without the dot notation?

Sconce answered 9/5, 2011 at 4:55 Comment(0)
S
77

Is it a bad idea to use the dot notation to initialize retain properties to nil in my init methods?

Yes, it is a bad idea.

1) The object has already been zeroed in the alloc+init sequence, so it is not necessary to assign it nil. In other words, this call is useless unless you have side effects in your accessors (side effects in accessors should also be avoided at this stage).

2) You should not message self with methods which are overridden while in partially constructed states (e.g. init and dealloc).

Is there a reason for #2? I often do self.array = [NSMutableArray array]; in my init methods.

The reason is that your object should not be interested in the class interface's behavior during partially constructed states (init..., dealloc, finalize, and many copyWithZone: implementations). your class should be interested in initializing properly (as in init...) and cleaning up after itself including its members (as in dealloc) without introducing side-effects.

consider this example, which you can build as a Foundation tool for OS X:

#import <Foundation/Foundation.h>

enum { UseItTheRightWay = true -OR- false };

@interface MONObjectA : NSObject
{
    NSMutableArray * array;
}

@property (nonatomic, retain) NSArray * array;

@end

@implementation MONObjectA

@synthesize array;

- (id)init
{
    self = [super init];
    if (0 != self) {
        NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
        if (UseItTheRightWay) {
            array = [NSMutableArray new];
        }
        else {
            self.array = [NSMutableArray array];
        }
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
    if (UseItTheRightWay) {
        [array release], array = nil;
    }
    else {
        self.array = nil;
    }
    [super dealloc];
}

@end

@interface MONObjectB : MONObjectA
{
    NSMutableSet * set;
}

@end

@implementation MONObjectB

- (id)init
{
    self = [super init];
    if (0 != self) {
        NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
        set = [NSMutableSet new];
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
    [set release], set = nil;
    [super dealloc];
}

- (void)setArray:(NSArray *)arg
{
    NSLog(@"%s, %@",__PRETTY_FUNCTION__, self);
    NSMutableSet * tmp = arg ? [[NSMutableSet alloc] initWithArray:arg] : nil;
    [super setArray:arg];
    [set release];
    set = tmp;
}

@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    [[MONObjectB new] release];

    /* the tool must be named 'Props' for this to work as expected, or you can just change 'Props' to the executable's name */
    system("leaks Props");

    [pool drain];
    return 0;
}

The main switch to toggle behavior in this test is UseItTheRightWay.

If UseItTheRightWay is true, we are given the result:

2011-05-09 01:52:11.175 Props[45138:a0f] -[MONObjectA init], <MONObjectB: 0x10010c750>
2011-05-09 01:52:11.177 Props[45138:a0f] -[MONObjectB init], <MONObjectB: 0x10010c750>
2011-05-09 01:52:11.179 Props[45138:a0f] -[MONObjectB dealloc], <MONObjectB: 0x10010c750>
2011-05-09 01:52:11.179 Props[45138:a0f] -[MONObjectA dealloc], <MONObjectB: 0x10010c750>
leaks Report Version:  2.0
Process:         Props [45138]
< --- snip --- >        
Process 45138: 1581 nodes malloced for 296 KB
Process 45138: 0 leaks for 0 total leaked bytes.

And if UseItTheRightWay is false, we are given the result:

2011-05-09 01:55:51.611 Props[45206:a0f] -[MONObjectA init], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.614 Props[45206:a0f] -[MONObjectB setArray:], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.615 Props[45206:a0f] -[MONObjectB init], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.617 Props[45206:a0f] -[MONObjectB dealloc], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.618 Props[45206:a0f] -[MONObjectA dealloc], <MONObjectB: 0x10010c750>
2011-05-09 01:55:51.618 Props[45206:a0f] -[MONObjectB setArray:], <MONObjectB: 0x10010c750>
leaks Report Version:  2.0
Process:         Props [45206]
 < --- snip --- >    
Process 45206: 1585 nodes malloced for 297 KB
Process 45206: 1 leak for 48 total leaked bytes.
Leak: 0x100110970  size=48  zone: DefaultMallocZone_0x100005000 instance of 'NSCFSet', type ObjC, implemented in Foundation 
    0x70294ff8 0x00007fff 0x00001080 0x00000001     .O)p............
    0x00000001 0x00000000 0x00000000 0x00010000     ................
    0x707612a8 0x00007fff 0x00000000 0x00000000     ..vp............

Problem #1

This example's obvious failure is the leak, introduced in dealloc.

Problem #2

The second thing that will bite you is subtler:

-[MONObjectA init]
-[MONObjectB setArray:]
-[MONObjectB init]

What's this??? -[MONObjectB setArray:] is called before -[MONObjectB init]? That means that MONObjectB's implementation is used before -[MONObjectB init], and even before -[MONObjectA init] has exited. That's no good =\

Nontrivial designs will just produce a bunch of undesirable side effects, weird behavior, leaks, and so on. Complex designs will fail in very obvious, and very subtle ways which can be very difficult to track down. it's best to avoid maintenance headaches over such trivial written differences, and to write classes the proper way from the start (even though you could get away doing this for quite some time, without obvious side effects).

Scoville answered 9/5, 2011 at 5:4 Comment(8)
Thanks. Is there a reason for #2? I often do self.array = [NSMutableArray array]; in my init methods.Sconce
Great demo! Never understood the implications until now.Sconce
@SeanDanzeiser could you be more specific? you could be asking a number of thingsScoville
im a bit new to this, sorry for being unclear. I was just wondering how you created the print out for UseItTheRightWay being true and false (the 'we are given the result' part)Hydrophobia
@SeanDanzeiser ah, ok. it's very unusual to do what i have done in this example. i did it so it would be self-contained. you should not use this approach in production code -- it can be useful for little proof of concepts. having said that, this will not work on iOS. however, there are generally more useful tools at your disposal -- notably, the Instruments->leaks. now to get to your question: look for the part that says system("leaks Props"); right before main returns -- that's like entering leaks SOME_APP_NAME in Terminal.app. the output was manually snipped, to keep the demo succinct.Scoville
@Scoville how would you handle an object that you initWithJSON and in there you call a method that does the decoding. Is it ok to call [self decodeFromJSON] in the init? (Assuming the decoding is not using properties)? Then do we doc the class and say not to override decodeFromJSON?Camouflage
@Camouflage i generally prefix private methods with the class name (as a convention, so you don't need to publish selectors which must not be overridden). calling private methods can be completely safe, although occasionally complex to follow. initializers are sooo specific, that their implementations are often poor candidates for reuse -- so the method decodeFromJSON may be unnecessary.Scoville
@Justin, could you, please, help me to understand how memory leak was introduced? I didn't catch thatRhodic

© 2022 - 2024 — McMap. All rights reserved.