PDA

View Full Version : Woohoo! First Thread! ... and an Obj-C Question


bassplayinMacFiend
2005-11-15, 15:22
Mmmm, can you smell that? It's that new forum smell! *takes deep whiff* :D

Got an Objective-C question for all of you:

In one of the methods in my Musical Scale program, I return the scaleArray like so:

return [scaleArray copy];

Now, scaleArray starts life as an NSMutableArray. When I return the copy, it is immutable, i.e. a NSArray. This isn't a problem, but a weird quirk seems to happen.

Instead of the objects in the array having retainCounts of 1, they have retainCounts equal to their retainCount from scaleArray +1. In order to release this returned array, I do the following:

for(i=0; i < [returnedArray count]; i++)
[[returnedArray objectAtIndex:i] release];

Then, once I've done what I need to do with the array I call

[returnedArray release];

This properly releases all the objects and removes a pesky memory leak that I spent some time tracking down. My question is, how do I get my initial array copy to not tack on the previous retainCounts from the original array? Or who knows, maybe this is some kind of feature in NSMutableArray? If it is hopefully it doesn't get omitted in a future release of OS X and cause my program to crash.

MCQ
2005-11-15, 18:45
Not really a Cocoa/Obj-C programmer, so I'll post a bunch of links and hopefully one of them will help. :)

Sounds like there's something with immutable versions of the Foundation classes that may do some optimization during copies, which might be doing something you wouldn't otherwise want:

http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Tasks/ImplementCopy.html#//apple_ref/doc/uid/20000049-999791

Sounds like some people were running into problems with this, though they didn't try mixing copy from NSMutableArray to NSArray:
http://www.idevgames.com/forum/archive/index.php/t-9250.html

Based on what I read, maybe doing a initWithArray:copyItems and setting the flag to YES would work, not sure though.

http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSArray.html#//apple_ref/occ/instm/NSArray/initWithArray:copyItems:

byzantium
2005-11-15, 19:04
The reason this happens; is that your 'copy' of the array; is only returning a new NSArray object (a shallow vs a deep copy). The objects within the NSArray are not copied, only their references.

However, since each reference to an object in the array needs to be retained by the array; the retain count on each object is incremented. You shouldn't have to decrement the counts on the objects when releasing your new array; it should decrement all the retain counts on the objects within that array.

Why are you returning a copy of your array anyway; why not just return the array pointer?

bassplayinMacFiend
2005-11-15, 20:32
The reason this happens; is that your 'copy' of the array; is only returning a new NSArray object (a shallow vs a deep copy). The objects within the NSArray are not copied, only their references.

Well, after I do my dual release, the original array still exists so I'm not releasing the original array items (or I'd segfault the next time I try to return an array copy). So I'm getting more then just pointers to the objects in the original array.

However, since each reference to an object in the array needs to be retained by the array; the retain count on each object is incremented. You shouldn't have to decrement the counts on the objects when releasing your new array; it should decrement all the retain counts on the objects within that array.

The problem is the retainCounts on my returned copied array are all at 2. The for loop decrements one of these retainCounts, and the release message decrements the second (last) retainCount.

Why are you returning a copy of your array anyway; why not just return the array pointer?

From some memory management tutorials I read on the web, I'm actually doing it the correct way. By returning a copy, I'm keeping the original array nestled inside its object. This way there's no chance of modifying an array member that belongs to a different object.

Basically I want to set up walls between objects so there's no possibility of object B screwing up what are essentially private members of object A. If I return a pointer to the array, then there is the possibility I could modify objects in that array and that wouldn't be good.

In a program as simple as my Musical Scales program this shouldn't be a problem, but I want each object to completely exist on its own. This way I can reuse my Note and ScaleGenerator objects in new projects without having to remember "Oh this object returns a pointer instead of a copy so I can't modify the stuff I get from this object."

Also, it was the only way I was able to successfully eliminate the Note object memory leaks that were occurring in my program.

[edit]
MCQ,

Thanks for the links. I'll check them out to see if I can come up with a better solution then the one I'm currently using. :)

Brad
2005-11-15, 21:01
From some memory management tutorials I read on the web, I'm actually doing it the correct way. By returning a copy, I'm keeping the original array nestled inside its object. This way there's no chance of modifying an array member that belongs to a different object.

Basically I want to set up walls between objects so there's no possibility of object B screwing up what are essentially private members of object A. If I return a pointer to the array, then there is the possibility I could modify objects in that array and that wouldn't be good.
And a very good convention/practice that is. :)

</reassuring>

byzantium
2005-11-16, 03:30
Ok, from the links provided by MCQ, copy: does return you a deep copy of the array and the objects. It's mutableCopy: that returns a shallow copy.

This is what I think is happening:
the array's objects are individually duplicated with copyWithZone: (which means they would initially have a refcount of 1)
the new array is initialized with the copy of old objects using the method initWithObjects: (this increments the refcount to 2)

This is the documentation for
"initWithObjects:
- (id)initWithObjects:(id)firstObj, ...

Initializes a newly allocated array by placing the objects in the argument list in it. This list is a comma-separated list of objects ending with nil. Objects are retained as they’re added to the array. After an immutable array has been initialized in this way, it can’t be modified. Returns an initialized object, which might be different than the original receiver."