tags:

views:

183

answers:

3

I'm still pretty new to Objective-C coding (as evidenced by this question) and I think I'm not completely understanding how using the retain attribute in a @property declaration works.

Here is an example class:

@interface Foo : NSObject {
    NSMutableArray *myArray; 
}

@property (retain) NSMutableArray *myArray;

My understanding was that adding the retain attribute to the @property declaration (and using the necessary @synthesize delcaration in the implementation file) will basically do the following setter and getter for me:

- (void)setMyArray:(NSMutableArray *)newArray {
    myArray = [[NSMutableArray alloc] initWithArray:newArray];
    [newArray release];
}

- (NSMutableArray *)myArray {
    return myArray;
}

Is this accurate or am I mistaken on how the retain attribute works?

A: 

retain will not do a copy of the new value. It will retain the new value and release the old one.

Mike Weller
+3  A: 

Adding the retain attribute will actually generate this code:

- (void)setMyArray:(NSMutableArray *)newArray {
    [newArray retain];
    [myArray release];
    myArray = newArray;
}

- (NSMutableArray *)myArray {
    return myArray;
}

The reason the retain method is called on newArray before release on the old value is that if newArray and myArray are the same object, the array will be released before it is retained again.

Philippe Leybaert
so if I override the setter method myself, I should follow this format of retain and release?
cpjolicoeur
@cpjolicoeur: That depends, the actual implementations are even more sophisticated with locks to prevent threading issues.
Georg
yes, that's exactly how the compiler generates it (I have been trying to find a reference to this, but I can't seem to find it right now)
Philippe Leybaert
I believe that the setter call is surrounded by `if (newArray == myArray)` to gain a little bit of performance when assigning the same object, but that's basically an implementation detail.
Georg
Are you sure about the return [[myArray retain] autorelease];. Won't this cause loads of objects to be hanging in the `AutoReleasePool` if you go on assigning new objects? if `self` was release all of it's `myArray` would still linger until the pool was killed, even though nothing else in the program was referencing them.
ruibm
Someone edited my post. I think it's wrong
Philippe Leybaert
Ah cool, that looks a lot better! Thanks.
ruibm
It's necessary. Imagine you've got some code and you access that property: `id name = theObject.name;` After that you change the name: `theObject.name = @"Someone";`. Now you try to access the old name, which has been released: `NSLog(name);` This is going to crash.
Georg
Apple does it this way, just try out the AppleScripts in Xcode to create accessors. I suppose it would also be possible to use autorelease in the setter instead of the getter. No idea how that works performance wise…
Georg
The code it generates is not exactly equivalent to that, but roughly. This code is nonatomic, at the very least. And I'm pretty sure the `[[myArray retain] autorelease]` edit was correct, as gs demonstrated.
Chuck
@gs: you have a point. We should disassemble the generated code to know for sure :-)
Philippe Leybaert
Try it: http://pastebin.com/f312a55f3. This prints for me: `*** _NSAutoreleaseNoPool(): Object 0x203c of class NSCFString autoreleased with no pool in place - just leaking` Here's some opensource code of Apple: http://www.opensource.apple.com/source/objc4/objc4-371.2/runtime/Accessors.subproj/objc-accessors.m
Georg
+1  A: 

It's really hard to do it right. Take a look at the article Memory and thread-safe custom property methods on Cocoa with Love by Matt Gallagher.

Here's one implementation that works, heavily inspired by that excellent article.

- (void)setSomeString:(NSString *)aString {
    @synchronized(self)
    {
        if (someString != aString) // not necessary, but might improve
                                   // performance quite a bit
        {
            [aString retain];
            [someString release];
            someString = aString;
        }
    }
}

- (NSString *)someString {
    @synchronized(self)
    {
        id result = [someString retain];
    }
    return [result autorelease];
}
Georg
I believe all the locking action only occurs if you declare it atomic.
ruibm
@ruibm: Atomic is the default. See http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/ObjectiveC/Articles/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17-SW28
Georg