Deallocating NSMutableArray objects - objective-c

This is my 'tick' function:
- (void) tick: (ccTime) dt
{
NSLog(#"%d",ticker);
if(fbut.Adown == YES && ticker > 4)//fbut is a button
{
elayer = [[effectsLayer alloc] init]; // each effectlayer draws a //projectile that moves forward 'x' ticks
elayer.e_playpos = glayer.playerpos; // player position
[self addChild:elayer z:2];
[mutable addObject: elayer];
[elayer release];
if(mutable.count > 20) // when there are 20 projectiles drawn, start //destroying the last one.
{
NSLog(#"mutable: %d", mutable.count);
[mutable removeLastObject];
}
ticker=0;
}
ticker++;
// . . .
this is what the running program looks like
http://screencast.com/t/LpNHL2kJIVpu
looks like more than 20..
interesting thing though, is that the array is holding steady at 20 objects. so if the objects are being 'removed' (via [mutable removeLastObject];) how come they show up on the screen?
Here's the next pickle...
Now i change init to retain( look for the *****'s )
- (void) tick: (ccTime) dt
{
NSLog(#"%d",ticker);
if(fbut.Adown == YES && ticker > 4)//fbut is a button
{
elayer = [[effectsLayer alloc] retain]; // *********each effectlayer draws //a projectile that moves forward 'x' ticks
elayer.e_playpos = glayer.playerpos; // player position
[self addChild:elayer z:2];
[mutable addObject: elayer];
[elayer release];
if(mutable.count > 20) // when there are 20 projectiles drawn, start //destroying the last one.
{
NSLog(#"mutable: %d", mutable.count);
[mutable removeLastObject];
}
ticker=0;
}
ticker++;
// . . .
And now no effect layers are being drawn, but still the NSArray holds 21 - 20 objects. all of these objects are uninitialized. so i added init to the end:
elayer = [[[effectsLayer alloc] retain] init];
now i have the same effects from before.
so i try autorelease..
same effect, lots and lots of pew pew pew's, way more than 20.
my goal is to only have 20 alowed to be drawn at a time and once the 20 are drawn they are deallocated. right now, with out deallocation, the program runs ok untill about 4 mins in when there are about 2000 e layers and the fps is about 5..
why won't the ship pew pew right?
(BTW i m using cocos2d frameworks) this is a project that is copyrighted by me, alex earley 2009.

First, [[effectsLayer alloc] retain] is terrible. Never do that. Ever. Never use an allocated object that has not been initialized. Furthermore, this will retain the object at least twice, since a call to +alloc returns a retained object, and you're retaining it again, then adding it to an array (which retains it a third time), but it's only getting released twice (when it's removed from the array and your singular release).
I suspect that the problem is with this line: [self addChild:elayer z:2]; What does this method do? Is it in charge of actually drawing the elayer onto the screen? If so, then that means its probably also retaining elayer, which means it's not getting deallocated, because you don't appear to be doing any sort of "removeChild" call when popping things out of your mutable array.
In short: just because something's no longer in your array doesn't mean it's also not on the screen.

Related

Deleting an Object after collision

I am making a game, and part of the game is a rabbit collecting eggs. I am having an issue with when the rabbit intersects the egg the game is crashing. the error I am getting is Collection <__NSArrayM: 0x17805eed0> was mutated while being enumerated.'
I have an image of 1 egg, and the egg appears after every couple of seconds, when the rabbit intersects the egg, I just want the egg to disappear and give 1 point.
Here is the code I am using
In the header file I have
#property (strong, nonatomic) NSMutableArray *eggs;
and the implementation file I have this for adding the egg
UIImageView *egg = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self gameView] frame]), holeBottom - 115 , 50, 60)];
[egg setImage:[UIImage imageNamed:#"easterEgg.png"]];
[[self gameView] insertSubview:egg belowSubview:[self counterLabel]];
[[self eggs] addObject:egg];
And this for detecting collision and trying to remove the egg
for (UIView *egg in [self eggs]) {
[egg setFrame:CGRectOffset([egg frame], [link duration]*FBSidewaysVelocity, 0)];
if ( CGRectIntersectsRect (rabbit.frame, CGRectInset ([egg frame], 8, 8))) {
[[self eggs]removeLastObject];
[self incrementCount];
}
}
I'm hoping you can see where I have gone wrong with this code and help me to rectify the problem.
Thank you in advance for your time
the error message if very clear that you cannot mutable collection (e.g. remove element) while enumerating it (e.g. using for in loop)
the easiest solution is to copy the collection and enumerate the copied one
for (UIView *egg in [[self eggs] copy]) { // <-- copy
// you can modify `[self eggs]` here
}
or
NSMutableArray *tobeRemoved = [NSMutableArray array];
for (UIView *egg in [self eggs]) {
if (condition)
[tobeRemoved addObject:egg];
}
[[self eggs] removeObjectsInArray:tobeRemoved];
Collection <__NSArrayM: 0x17805eed0> was mutated while being enumerated is being caused because you are looping over an array, while at the same time deleting the objects in that array. There are a few ways to get around this, one way is to create a new array of the objects that you want to delete while looping over the original array eggs and after the loop is finished, looping over this new array and performing the remove.
Code example:
NSMutableArray *eggs;//this is full of delicious eggs
//...
NSMutableArray *badEggs = [NSMutableArray array];//eggs that you want to removed will go in here
for(NSObject *egg in [self eggs]){
if([egg shouldBeRemoved]){//some logic here
[badEggs addObject:egg];//add the egg to be removed
}
}
//now we have the eggs to be removed...
for(NSObject *badEggs in [self badEggs]){
[eggs removeObject:badEgg];//do the actual removal...
}
note: your line of code [[self eggs]removeLastObject]; looks like a mistake in any case... this removes the object at the end of the array (I don't think you want to do this...)

Objective-C: Redrawing objects on screen

I've got a GameScreen.m file like (this is a simplified piece of the code):
- (IBAction) onCellClick:(id) sender
{
points +=1;
self.myScore.text = [[NSNumber numberWithInt: points] stringValue];
//myScore is a label in GameScreenViewController xib
}
That is, upon clicking a cell in the view, it will increase a text label by 1. So far so good.
then, in the same code, I've got a timer:
- (void) startTimer
{
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
its updateCounter method is:
- (void) updateCounter:(NSTimer *)theTimer
{
int seconds;
static int count = 0;
count +=1;
timeElapsed = [[NSString alloc] initWithFormat:#"%d", seconds + count];
self.time.text = timeElapsed;
//time is a label in GameScreenViewController xib
}
the thing is that "time" label is not updated (1 sec each time) in this case. I've inserted an AlertView to check if the startTimer method is valid and correctly called, and it actually is (it shows an annoying alertview each second with the timeElapsed value). However, I can' get the time label value to be changed.
Why is my score label updated upon action, while time label isn't updated every second? Is there any way I can update it without including my code in the ViewController?
//note: my coding splits into three files: the appDelegate flips screens and sends values among them; my viewControllers just the windows and, finally, my GameScreen class manages all the processes. From the xib, File's Owner is connected to the ViewController, and the view is connected to GameScreen class.
Thanks a lot for any feedback, please feel free to ask for any piece of additional code needed.
You have to do that (UI related operations) in main thread.
Instead of the line,
self.time.text = timeElapsed;
do as follows:
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed waitUntilDone:NO];
Edit:
- (void) updateCounter:(NSTimer *)theTimer
{
//int seconds;
static int count = 0;
count +=1;
NSString *timeElapsed1 = [[NSString alloc] initWithFormat:#"%d", count];
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed1 waitUntilDone:NO];
[timeElapsed1 release];
//time is a label in GameScreenViewController xib
}
I have gone through an UGLY walkaround. It works to some extent, but I went through such a crappy fix that I'm too embarassed to share...
Basically, I moved my timer directly to ViewController since I want it to be fired upon view load and can't get it to work with a call from ViewController's -(void)viewDidLoad to GameScreen's -(void) startTimer. All other stuff involving both methods is, pretty much, duplicated (ok, not duplicated, let's say 'polymorphed' since I handle some variables to fire them).
It seems my GameScreen.m IBActions can only fire other methods within my GameScreen.m, not on GameScreenViewController.m. Thus, I'm handling my buttons' behavior on GameScreen.m and, on GameScreenViewController.m, I just handle 'automatic' stuff; that is, anything not depending on user interaction. It made me have some IBOutlets duplicated depending on input/output needed so I guess that, since it's now working, you can't tell the difference if you don't go under the hood...
Thanks everyone for their feedback though.

Pesky leak in setter/getter methods

It seems like I keep asking the same questions, memory related. My current code works exactly as I intend it, but I cannot figure why I am showing a leak here in Instruments.
-(NSDate *)startTimeAndDate {
NSDate *dateToReturn = nil;
if (startTimeAndDate != nil) {
dateToReturn = [startTimeAndDate retain];
} else { //is currently nil, this will be the initial setting
//return default time if we have a working date
if (finishTimeAndDate != nil) {
dateToReturn = [[self dateFromDate:finishTimeAndDate withNewTime:defaultStartTime]retain];
} else {
//return the default time with today's date if we have nothing set as yet
dateToReturn = [[self dateFromDate:[NSDate date] withNewTime:defaultStartTime] retain];
}
//save the initial setting
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
}
[startTimeAndDate release];
startTimeAndDate = dateToReturn;
return startTimeAndDate;
}
-(void)setStartTimeAndDate:(NSDate *)inStartTimeAndDate {
BOOL initialAssignment = NO;
if (startTimeAndDate == nil) {
initialAssignment = YES;
}
if (startTimeAndDate != inStartTimeAndDate) { //skip everything if passed object is same as current
//check that the start time is prior to finish only if finish time has been entered
NSDate *dateToSetStartTo = nil;
if (finishTimeAndDate != nil) {
if ([inStartTimeAndDate earlierDate:finishTimeAndDate] == inStartTimeAndDate) {
// use the new time, it is earlier than current finish time
dateToSetStartTo = [inStartTimeAndDate retain];
} else { //start time is not earlier then finish time
// the received entry is invalid, set start time to 1 default interval from finish
dateToSetStartTo = [[finishTimeAndDate dateByAddingTimeInterval:-self.defaultTimeInterval] retain];
}
} else { //finish time is nil
// use the new time without testing, nothing else is set
dateToSetStartTo = [inStartTimeAndDate retain];
}
[startTimeAndDate release];
startTimeAndDate = dateToSetStartTo;
}
if (initialAssignment) {
self.initialStartDateAndTime = [[self.startTimeAndDate copy] autorelease];
}
}
So far as I can see, I am balancing all retains with release or autorelease. The leak appears to be caused on the first pass only. I have a view controller, it creates my model (wherein this code lies) and sets a start date, nothing else is done at that point. If I close that view controller at that point, Instruments shows that I am leaving the date object as a leak.
I placed a NSLog to show retain count at dealloc and, sure enough, it has retain count of 2 before my final release is called, leaving a retain count of 1 when it should have been destroyed. It is always the same regardless if I close immediately after initialization or set and get a hundred times. retainCount is 2 prior to my final call to release in dealloc.
I have been looking at this all weekend and cannot figure where I've gone wrong.
To clarify, the initial call is to set the startTimeAndDate property. At that point all other fields are nil or 0 if not objects. That startTimeAndDate object appears to be the leaking object.
Firstly, can you describe the problem you are trying to solve with this code? I ask because it appears very complex and my initial thought is that simplification will not only clarify what you are doing, but is also likely to solve your leak as well.
Secondly, (and I may have this wrong), you only need to retain/release objects if you expect those objects to exist beyond the scope of the method, or you expect that they may be released by some code that you are claling in you method. Based on this, you appear to be over retaining and releasing in your code. I think you can remove a lot of it.
Again I may be wrong, but it appears that you will indeed leak. The reason I think so is this - on your first pass you retain some data in dateToReturn which is a local variable. Then you do
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
But this is not releasing dateToReturn. Instead it is releasing the copy of dateToReturn. dateToReturn is still retained. Presuming that you intend to autorelease the copy because initialStartDateAndTime is set with retain, I think you should be doing:
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
[dateToReturn release];
Of course, if you remove the extra retain/release's then this becomes simpler again.
The final thing I would suggest is around naming. The problem with code like this is that you have a number of methods and variables, all with very similar names. This can make it difficult to follow and lead to bugs. So ask yourself if you really need this many variables. And can you make your code more readible by changing some of the names.
Dam, ignore what I said. I just went through the code again and you're right. I think you are basically being burned by the complexity of the code. I found it quite difficult to follow, especially with the number of properties. I think what I would do at this stage is to copy the code to a unit test and run it from there. Then you can better test and debug it. I would recommend GHUnit if you do not already have unit testing in place.
The other thing that occurs to be is that there is code executing somewhere else in your program that is retaining the date. Therefore triggering the leak. For example if inStartTimeAndDate is coming in with a retain count of 1, but is not released by the code that called the setter then you could end up with startTimeAndDate with a retain of 2.
Having said that, here's my rewrite of the getter in an attempt to clarify whats going on:
-(NSDate *)startTimeAndDate {
// If we have it, bail out fast.
if (startTimeAndDate == nil) {
return startTimeAndDate;
}
// Is currently nil, this will be the initial setting
NSDate *dateToReturn = nil;
//return default time if we have a working date
if (finishTimeAndDate != nil) {
dateToReturn = [self dateFromDate:finishTimeAndDate withNewTime:defaultStartTime];
} else {
//return the default time with today's date if we have nothing set as yet
dateToReturn = [self dateFromDate:[NSDate date] withNewTime:defaultStartTime];
}
//save the initial setting
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
startTimeAndDate = [dateToReturn retain];
return startTimeAndDate;
}
The main reason for this re-write was that it appeared that if there was a startTimeAndDate then the code was doing this:
dateToReturn = [startTimeAndDate retain];
...
[startTimeAndDate release];
startTimeAndDate = dateToReturn;
Which seemed a little pointless because it's effective doing a retain, release and self assignment. It would work, but there's less chance of a bug if we leave it out.

CCSprite not deleting

I have a class called Projectiles which inherits from CCSprite class,
Currently there are 2 types of projectiles, rain1 and rain2.
I have a method that creates a bunch of these sprites every 2 seconds to give the illusion of pulsating rain.
Each one of these rain sprites is added to the array, _projectiles and it is effected by gravity.
In fact, its working just about perfectly, except for the memory management and soon after this rain loop keeps creating sprites I get massive frame rate drops.
Ideally, if the rain (under the constant of gravity) drops below the height of the screen, I want the rain sprite to be deleted. Deleted from the _projectiles array, deleted from the view completely!
My code isn't doing this! Please I need some assistance...
Here is a snippet:
for (Projectile *rain1 in _projectiles){
if (rain1.position.y < -winSize.height) {
rain1 = nil;
[_projectiles removeObject: rain1];
[self removeChild:rain1 cleanup:YES];
[rain1 release];
}
}
for (Projectile *rain2 in _projectiles){
if (rain2.position.y < -winSize.height) {
rain2 = nil;
[_projectiles removeObject: rain2];
[self removeChild:rain2 cleanup:YES];
[rain2 release];
}
}
remove the rain1 = nil, that should work.
you change the rain1 pointer to nil, thats why when you call [self removechild] it cannot find the rain1 sprite to remove.

Objective-C code not deallocating memory!

I'm trying to learn Objective-C. I almost finished one excercise but it is not deallocating the memory:
This is is what I have:
void PrintPolygonInfo() {
NSLog(#"--------------------");
NSLog(#" PRINT POLYGON INFO");
NSLog(#"--------------------");
NSMutableArray *array = [[NSMutableArray alloc] init];
PolygonShape *p1 = [[PolygonShape alloc] initWithNumberOfSides:4 minimumNumberOfSides:3 maximumNumberOfSides:7];
PolygonShape *p2 = [[PolygonShape alloc] initWithNumberOfSides:6 minimumNumberOfSides:5 maximumNumberOfSides:9];
PolygonShape *p3 = [[PolygonShape alloc] initWithNumberOfSides:12 minimumNumberOfSides:9 maximumNumberOfSides:12];
[array addObject:p1];
[array addObject:p2];
[array addObject:p3];
// Log the descriptions
for (id shape in array) {
NSLog(#"%#", shape);
}
// Test the constraints
for (PolygonShape *shape in array) {
[shape setNumberOfSides:10];
}
[p1 release];
[p2 release];
[p3 release];
}
This is the dealloc():
- (void) dealloc {
NSLog(#"Deallocated!!!");
[super dealloc];
}
And this are the results:
2009-11-19 06:58:17.030 Assignment 1B[5441:a0f] --------------------
2009-11-19 06:58:17.030 Assignment 1B[5441:a0f] PRINT POLYGON INFO
2009-11-19 06:58:17.031 Assignment 1B[5441:a0f] --------------------
2009-11-19 06:58:17.031 Assignment 1B[5441:a0f] init: Retain count of 1.
2009-11-19 06:58:17.032 Assignment 1B[5441:a0f] init: Retain count of 1.
2009-11-19 06:58:17.032 Assignment 1B[5441:a0f] init: Retain count of 1.
2009-11-19 06:58:17.033 Assignment 1B[5441:a0f] Hello I am a 4-sided polygon (aka a Square) with angles of 90 degrees (1.570796 radians).
2009-11-19 06:58:17.033 Assignment 1B[5441:a0f] Hello I am a 6-sided polygon (aka a Hexagon) with angles of 120 degrees (2.094395 radians).
2009-11-19 06:58:17.034 Assignment 1B[5441:a0f] Hello I am a 12-sided polygon (aka a Dodecagon) with angles of 150 degrees (2.617994 radians).
2009-11-19 06:58:17.034 Assignment 1B[5441:a0f] Invalid number of sides: 10 is greater than the maximum of 7 allowed
2009-11-19 06:58:17.035 Assignment 1B[5441:a0f] Invalid number of sides: 10 is greater than the maximum of 9 allowed
As you can see, it is not printing the 'Deallocated!!!" message:
Can anyone tell me what I am doing wrong please?
Thanks in advance
The array retains the objects it contains. The objects can only be deallocated once they are removed from the array, or the array is released.
You're leaking your array. You create it with alloc/init, but never release it. Either release it, or create it with [NSMutableArray array].
Also, the dealloc method is called on objects when they're released. However, it looks like you're using a function (not a method of an object) to do that stuff. How is the rest of your code set up?
Note that if you're application terminates, the dealloc method might not be called. The application's memory is cleared anyways and OSX might decide it's faster to just terminate the app and clear the memory without going through all deallocs.
This is documented in the NSObject reference.