NSMutableArray of sprites, memory issues? - objective-c

I have an array of ten sprites; they all sit in the same area of the screen For ease of explanation, I'll say at the bottom-middle, stacked one on top of each other. What I need to do is take the top sprite and throw it off the screen by swiping it forward (bottom to top). When I have a single sprite, I don't have an issue with this; it's when I have multiple sprites that I have the issues. I add the items to the layer like so:
for(int i=0; i<maxCount; i++){
CCSprite *x = [listOfItems objectAtIndex:i];
//NSLog(#"(%f, %f)", x.position.x, x.position.y);
[self addChild:x];
}
They are created in a step above:
for (int i=0; i<maxCount; i++) {
CCSprite *o = [CCSprite spriteWithFile:#"image.png"];
o.position = ccp(windowSize.width/2,windowSize.width/2);
[listOfItems addObject:o];
[o release];
}
I guess the real issue is I don't know how to handle the
"current" top. So, if I have array index 0 as the top, I can just go ahead and use the touch gesture and "flick" it off the screen. The first one works all right, but once I touch the second one it crashes with EXC_BAD_ACCESS. Here's the TouchesBegan method:
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
startingPoint = location;
//Trying something here....
//if(current)
// [current release];
current = [listOfItems objectAtIndex:currentPosition];
[self reorderChild:current z:2];
actionStartTime = [NSDate timeIntervalSinceReferenceDate];
}
When the "Trying something here..." lines are uncommented, that's where the crash happens, and when they are commented out, it happens when assigning current. I know I'm missing something here, but I just can't figure it out.

If your objects are are all added in an array I find the easiest thing to do is set a different tag to each object.
for(int i = 0; i < NUMOFOBJECTS; i++)
{
CCSprite *sprite = blah blah whatever;
// I do something like 100+i so that each time I want to
// create different types of objects I can subtract the "100"
// or whatever it is to get a base index value for arrays.
sprite.tag = 100+i;
}
Inside of your CCTouchesBegan you check the touch to make sure it is on the items and then you iterate through your items there and move only the item with the correct tag.
for(CCSprite *sprite in objects)
{
if(sprite.tag - 100 == [objectOrderList objectAtIndex:0].tag)
{
// Do whatever
}
}
Treat that as pseudo code as I am just writing off the top of my head. In this situation you would keep track of object order in an NSMutableArray, removing/replacing/adding objects whenever order is changed.

One point would be to remove [o release]; CCSprites are created as autoreleased objects.
Also, to find the reason of a crash, try opening the Debug Console which is under the Run menu. The last thing in the console will often display a message with why the error occured.

Look at how you set current — it's set to [listOfItems objectAtIndex:currentPosition], which is an object you don't own. So by the next go-round, that object might have been deallocated, and sending it any message once it's deallocated (including release) is undefined behavior, which will result in EXC_BAD_ACCESS if you're lucky. (It can result in much subtler and harder to catch bugs if you're unlucky.)

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...)

NSMutableArray Resetting Itself?

I am having an issue with NSMutableArray wiping its contents.
Consider my code: (int i; is in my file's .h as is NSMutableArray* newFileControllerArray)
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [[NSMutableArray alloc]init];
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"%lu",(unsigned long)elementsInArray);
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"THERE ARE %lu ELEMENTS IN THE ARRAY",(unsigned long)elementsInArray);
}
The first method called (other than awakeFromNib:) is newFileMenubar: This will add one element to the array. I can confirm that this works because 1 is printed in the console. However, once OKbutton is called and I print out the number of elements in my array it says that no elements are in the array. Why is that?
Am I missing something very obvious here? Why does my array reset itself?
EDIT:
The comments have gotten long and unwieldy so here is the code w/NSLogs and outputs:
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [NSMutableArray array];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
i++;
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
[[newFileWindowControllerArray objectAtIndex:i]close];
}
When the program launches, this is the output: self=0x100141480, array=0x100140f30
This should be coming from awakeFromNib:
The next method called is newFileMenubar:
The output from this is
self=0x1001ac990, array=0x1005228a0 and immediately after self=0x100141480, array=0x100140f30
The last method called is OKButtonClicked:
The output from the last method (OKButtonClicked:) is self=0x1001ac990, array=0x1005228a0
As you can see from the code, the name of the array doesn't change, but my outputs beg to differ? What could cause this?
There are good clues in your log output. There are multiple instances of the view controller (see the different values for 'self'?). They each have their own array. See this code...
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
When you press the button associated with that action, your app builds another view controller and places it in the array. That view controller gets the awake from nib message and allocates another array, and so on.
To confirm this, change the code as follows:
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:#"Hello world"];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
In the other methods, comment out your expectations that the array has anything other than strings in it, and see what you get. e.g. ...
- (IBAction)OKButtonClicked:(id)sender {
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i]close];
// instead...
NSLog(#"danh thinks my array will be ok: %#", newFileWindowControllerArray);
}
You probably do not mean to create another view controller on every button press, but I'm not sure what function you do want. Maybe you want an array of views? (To create many view controllers under the control of another, you'll want to read up on container view controllers, here).

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.

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.

Deallocating NSMutableArray objects

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.