Is it ok to retain and autorelease at the same time? - objective-c

I have the following code:
#interface MyClass : NSObject
{
NSMutableArray *items;
}
#end
#implementation MyClass
-(Item *)getItem
{
if(items.count < 1)
{
[self buildItemsArray];
}
Item *item = [[[items objectAtIndex:0]retain]autorelease];
[items removeObjectAtIndex:0];
return item;
}
-(void)buildItemsArray
{
// ...
[items addItem:someNewItem];
[items addItem:someOtherNewItem];
}
#end
I have a function that returns an item. If the items go down to 0, then the items array is built again inside buildItemsArray. My game logic requires that when I return an item, I need to remove it from the array. So, I am using a retain to ensure that the item is valid till the return line (since the only other known retain happened when the item was added to the items array), and an autorelease to ensure it gets cleaned up later. I have checked that this code neither crashes nor leaks. I wonder if:
a) This is OK - the reason I ask is that I have predominantly seen code with alloc/init/autorelease, and haven't run into this case with retain/autorelease
b) Is there some reason to alloc/init/autorelease instead:
Item *item = [[Item alloc]initWithItem:[items objectAtIndex:0]autorelease];
Thanks

This is generally ok:
Item *item = [[[items objectAtIndex:0] retain] autorelease];
[items removeObjectAtIndex:0];
return item;
Though this would make the intent clearer (returning an autoreleased object):
Item *item = [[items objectAtIndex:0] retain];
[items removeObject:item];
return [item autorelease];
No reason to alloc/init. That just adds unnecessary overhead.
Like I said in my comment, if this is a (relatively) new project, you really really really should be using ARC and not worry about these things anymore.

Related

Releasing Returned object Error in Objective-c

I have this code wherein I allocate a NSMutableIndexSet and return the object inside this set to be used for other methods. Here is the code:
NSMutableIndexSet *indexes = [NSMutableIndexSet new];
for (NSNumber * number in bookmarksArray)
{
[indexes addIndex:[number intValue]];
}
return indexes;
Upon researching on how to release returned objects, I edited the code above to this:
return [indexes autorelease];
However, it gave me an error saying that the message was sent to a deallocated instance. What should I do about this? Any suggestions?
Autoreleased instances are released when the run loop gets to them (or when you drain your autorelease pool manually). This means that the method that receives the instance of NSMutableIndexSet returned from your method needs to retain it before relinquishing the control to the run loop. Otherwise the instance will get released, rendering whatever references that you might have invalid.
There is a shortcut for calling [NSMutableIndexSet new] and then autoreleasing the instance: you can invoke indexSet class method like this:
NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
// Populate the indexes...
return indexes;
The problem maybe like the instance you got from this method is not retained if you set it to ivar or so.
return [indexes autorelease]; //this is ok.
but within other methods, you call this method to get the indexes, if you want the returned instance indexes to be remain out of that method, you should retain it. because the instance you got is autoreleased, it will dealloced out of the method generally.(next run loop here)
NSMutableIndexSet *iVarIndexes;
- (void)otherMethod
{
iVarIndexes = [[self getBookmarkIndexes] retain]; //for later use
}
If you are using Automatic Reference Counting (ARC) then you should use:
- (NSMutableIndexSet *)bookmarkIndexes
{
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
for (NSNumber *number in bookmarksArray)
{
[indexes addIndex:[number intValue]];
}
return indexes;
}
If you are not using ARC then you do indeed need to auto release:
- (NSMutableIndexSet *)bookmarkIndexes
{
NSMutableIndexSet *indexes = [[[NSMutableIndexSet alloc] init] autorelease];
for (NSNumber *number in bookmarksArray)
{
[indexes addIndex:[number intValue]];
}
return indexes;
}

Remove all items from UITabBarController

I'd like to remove all items from UITabBarController within MainWindow. I can achieve it this way:
self.tabViewController.viewControllers = [NSArray array];
self.tabViewController.customizableViewControllers = [NSArray array];
But what about old controllers that was there? Is this more correct way?
- (void)cleanCurrentTabbar {
for (id ctrl in self.tabViewController.customizableViewControllers) {
[ctrl release];
}
for (id ctrl in self.tabViewController.viewControllers) {
[ctrl release];
}
self.tabViewController.viewControllers = [NSArray array];
self.tabViewController.customizableViewControllers = [NSArray array];
}
Your second option is more correct concept wise as well with respect to memory management because it releases all allocated resources before making it nil.
But rather than giving it an empty array which is in itself an autoreleased object like [NSArray array], you can assign nil.
- (void)cleanCurrentTabbar {
for (id ctrl in self.tabViewController.customizableViewControllers) {
[ctrl release];
}
for (id ctrl in self.tabViewController.viewControllers) {
[ctrl release];
}
self.tabViewController.viewControllers = nil;
self.tabViewController.customizableViewControllers = nil;
}
Also as James Webster has said in blow comment:
"You may or may not need to release depending on the type of property viewControllers and customizableViewControllers are"
Hope this helps you.
That's cleaner in terms of memory yes. You should release objects when you are finished with them, that includes before reassigning.
However, I notice you are using self.tabViewController.viewControllers. Is that property assign or retain? If it's retain, the release will be done internally.
As #Parth Bhatt told, releasing viewControllers items looks like good idea. Maybe. But in my case it leads to the strange EXC_BAD_ACCESS errors:
So I end up with self.tabViewController.viewControllers = nil; and its works just fine.

iPhone EXC_BAD_ACCESS with NSMutableArray of strings

Problem context is a ViewController with several button handlers and a scores.list data file of 1000 NSString objects. If I click on buttonOne, the handler code checks if the file scores.list exists in the User Documents directory. If yes, it loads the data in an NSMutableArray called score, if not it creates the NSMutableArray in memory (to be stored on disk later) like this:
- (void)readScores
{
// Setup path + filename pathUserDocDirScorelist);
...
// test for presence scores.list in documents dir
if ([fileManager fileExistsAtPath: pathUserDocDirScorelist])
{ // Read scores.plist into NSMutableArray
score = [NSMutableArray arrayWithContentsOfFile:pathUserDocDirScorelist];
} else { // Initialize empty scores array with 1000 empty NSString entries
score = [[NSMutableArray alloc] init];
int i;
for(i = 0; i < 1000; i++) {
[score addObject:#""];
}
}
// at this point there is always a valid array score with 1000 entries
}
Basically this code works; in both cases (reading data from scores.list or in-mem build-up) I can verify in the debugger (with 'po score') that an array with 1000 entries is present afterwards.
Now comes the problem that is blocking me for 2 days now:
In the handler of buttonTwo, statements like [score count] crash, but only in case the array score gets its data from disk, not if build-up in memory. In the first case is the array still valid though until the last line of handler code of buttonOne, but then 'evaporates' as soon as the array is addressed in a next handler (EXC_BAD_ACCESS).
No, it is not caused by a premature release statement, since there are none (yet) in my entire app. :)
(not concerned with memory leaks yet).
How is this possible that within one view a NSMutableArray is valid at the end of button handler 1, but invalid at the beginning of the next button handler 2 if no explicit release statement is executed in between?
Extra info:
ViewController.h:
#interface ViewController : UIViewController {
NSMutableArray *score;
...
}
#property (nonatomic, retain) NSMutableArray *score;
...
- (IBAction) buttonOne: (id) sender;
- (IBAction) buttonTwo: (id) sender;
#end
And in ViewController.m I have:
#synthesize score;
...
- (IBAction) buttonOne: (id) sender {
if (score == nil) {
[self readScores];
}
...
NSLog(#"Number of entries in score = %i", [score count]); // never crashes
}
- (IBAction) buttonTwo: (id) sender {
NSLog(#"Number of entries in score = %i", [score count]); // **crash point**
}
P.S. I tried NSZombieEnabled by starting the app with alt/cmd/R and adding 'NSZombieEnabled=YES', but that does not result in extra information in the debug console.
It's always a little dangerous to do this:
score = [[NSMutableArray alloc] init];
outside of an init method. Because, the problem is that perhaps your method readScores is executed twice. I'd guess it almost certainly is.
What happens then is the the first scores object is never released. That causes a memory leak and sooner or later you get the dreaded EXC_BAD_ACCESS.
So, the best thing is either to first check:
if (score)
{
[score release];
}
Or, alternatively, set up scores as a synthesized retained object in your .h file. Then you can replace your code as follows and let everything happen automatically:
self.scores = [NSMutableArray array];
Note that here, I didn't use:
self.scores = [NSMutableArray alloc] init];
because that would cause two retains to happen, and of then EXC_BAD_ACCESS due to over-retention.

Issue with NSMutableArray visibility / retain

Alright so I am a little new to the NSMutableArray class and I think I am missing something obvious. I have an object pass a NSMutable Array to my window controller like so in my.m:
summaryWindow = [[SummaryWindowController alloc] init];
[summaryWindow setGlobalStatusArray:globalStatusArray];
I have the receiver method in the summaryWindow object as so:
-(void)setGlobalStatusArray:(NSMutableArray *)myArray
{
if ([myArray count] >0) {
if (globalStatusArray) {
[globalStatusArray release];
}
globalStatusArray = [[NSMutableArray alloc] initWithArray:myArray];
NSLog(#"Summary Window Init with new array: %#",globalStatusArray);
I see the NSLog no problem, and in that same object (summaryWindow) I have the following method:
- (NSMutableArray *)getGlobalStatusArray
{
return globalStatusArray;
}
Now I have globalStatusArray declared in my .h file as
NSMutableArray *globalStatusArray;
So shouldn't This be retained because I am using: initWithArray?
When I try to access this value in an another IBAction method:
- (IBAction)refreshButtonClicked:(id)sender
{
NSLog(#"The user has clicked the update button");
[ aBuffer addObjectsFromArray: globalStatusArray];
NSLog(#"Buffer is currently:%#",aBuffer);
[tableView reloadData];
}
The NSMutable array is null
2011-08-18 10:40:35.599 App Name[65677:1307] The user has clicked the update button
2011-08-18 10:40:35.600 App Name[65677:1307] Buffer is currently:(
)
I have tried using my own method to get the value i.e. [ self getGlobalStatusArray] to but I am missing something huge. FYI aBuffer is also declared in my .h ,
As albertamg noted, that looks like an empty array rather than nil, and a released object doesn't magically become nil under normal circumstances anyway.
This smells strongly of two different objects. Try logging self in your methods and see if one instance is getting the array and another is interacting with the UI.
This code isn't doing anything useful:
if ([myArray count] >0) {
if (globalStatusArray) {
[globalStatusArray release];
}
globalStatusArray = [[NSMutableArray alloc] initWithArray:myArray];
If the count of the old array is zero, it's leaking the actual array object. If the count is not zero, then it's releasing it properly. Just do the release and don't bother counting.
Are you sure there's actually something in myArray?
joe

Objective C Reassignment/Memory Management Crash

As a relative Objective-C beginner, I'm obviously still not grasping certain memory management rules. I can't figure out how to make this not crash:
#interface MyClass { NSArray *playerArray4th; }
- (void) viewDidLoad { playerArray4th = [self getAudioPlayersForSoundFile:#"rimshot" ofType:#"aif"]; }
- (NSArray*) getAudioPlayersForSoundFile:(NSString*)soundFileName ofType:(NSString*)soundFileType {
//code instantiating objects....
NSArray *toRet = [[NSArray alloc] initWithObjects:toRetTickPlayer,toRetTickPlayerCopy,toRetTickPlayerCopy2,toRetTickPlayerCopy3, nil];
return toRet;
}
Then later, in a different function:
NSArray *currentArray = playerArray4th;
[currentArray release];
currentArray = nil;
currentArray = [self getAudioPlayersForSoundFile:fileName ofType:ofType];
And it crashes when trying to access the array again:
- (void) playSound:(NSString*)soundType {
AVAudioPlayer *currentPlayer;
if ([soundType isEqualToString:#"4th"]) {
if (playerArray4thCounter >= [playerArray4th count]) playerArray4thCounter = 0;
NSLog(#"Playing from array: %#",playerArray4th);
currentPlayer = [playerArray4th objectAtIndex:playerArray4thCounter];
playerArray4thCounter++;
}
}
Try to learn about properties and about using getters and setters. Don't take shortcuts unless you know exactly what's going on.
So define the playerArray4th property in your header file:
#property (nonatomic,retain) NSArray *playerArray4th;
And then in your .m file create getter/setter:
#synthesize playerArray4th;
Then, always use self.playerArray4th for assigning and getting the variable. The prior objects will be released when needed.
So this will not leak:
self.playerArray4th = [NSArray arrayWithObjects:#"text",#"text",nil];
self.playerArray4th = [NSArray arrayWithObjects:#"new array",#"text",nil];
because the second assignment releases the first array.
Furthermore, read about using autorelease. In short, if you alloc, copy or new, you should either release or autorelease. There's a lot to read about this here on SO which I will not repeat here now.
Don't forget to put self.playerArray4th = nil; in your dealloc method.