NSMutableArray and memory dealloc - iphone

Im making an app for the iphone using cocos2d and i am trying to figure out the best approach for removing items from a NSmutableArray and from the layer at the same time.
What i mean by this is that the objects within the array inherit from ccNode and contain a ccsprite which i have added as a child to the cclayer. The below code is in a cclayer that has the nsmutablearray called bonusicons.
-(void) AddNewBonusIcon: (int) colour :(int) pos{
BonusIcon *newbonus;
CGSize winSize = [[CCDirector sharedDirector] winSize];
int maxX = winSize.width;
int maxY = winSize.height;
int posX, posY;
newbonus = [[BonusIcon alloc] init];
[newbonus setBonusColour: colour];
int bonusOffset = 0;
posX = anchorX;
posY = anchorY;
bonusOffset = [bonusIcons count]*([newbonus.bonus_sprite boundingBox].size.width/2 + 12);
newbonus.bonus_sprite.position = ccp(posX+bonusOffset,posY);
[newbonus.bonus_sprite setTag:pos];
[self addChild:newbonus.bonus_sprite];
[bonusIcons addObject:newbonus ];
[newbonus release];
}
This appears to do what i want for adding the objects sprite to screen and adding the objects to the nsmutablearray. Now of course this is probably not the correct way to do it so shout at me if not!
next i try to delete the objects from the array and from the screen. I can delete them from the array with no problems i just do the following
for (int i = INITIAL_BONUSES-1; i>=0; i--) {
[bonusIcons removeObjectAtIndex:i];
}
this of course leaves the sprites on screen. so how do i approach what i am trying to do so that i can remove both the sprites from screen and the objects from array that the sprite is associated with. I can remove the sprites from the screen by using the tags and typing
[self removeChildByTag:i cleanup:YES]; but then i get errors when trying to remove items from the array . i assume because i have deleted a part of the object already and the dealloc of the ccnode can no longer find the sprite to release?
so any pointers/tips etc of how i should be doing this would be much appreciated. I have read a bunch of stuff on memory management which i believe is my current issue but i just dont seem to be getting it right.
thanks all
edit: ok since posting this i have removed the sprite dealloc from the ccnode itself and added it to the cclayer above it. This has stopped the crashing so i guess i was right with the problem i was having. I of course do not think the way i solved it is the most ideal way but it will do until i find a better way.

You don't have it in the code you posted, but your question seems to strongly imply that you are calling dealloc. The only place you should ever call dealloc is [super dealloc] at the end of a class's dealloc method. Calling it on anything but super or in any other place is wrong and will lead to errors about prematurely deallocated objects (because, well, that's what it does).
If this is what you're doing, I strongly suggest you read Apple's memory management guide. It lays out how memory management works in Cocoa very simply yet thoroughly.

Related

Cocos2D calling an object init from a scene layer init

Hi I'm currently working on an iPhone game, top-down Strategy RPG (kinda like Fire Emblem), I have my tiled map set up, and the gameplay layer and some characters and enemies set up and drawn on the screen and moving around. My question really just to help wrap my head around how I can initialize my characters easliy. My character init is simple, it just loads the animations and set the stats as such:
//Hero Class
-(id)init
{
if(self = [super init])
{
characterClass = kHeroClass;
[self initAnimations];
[self declarePlayer:Hero withLevel:1 withStrength:15 withDefence:14 withMindpower:15 withSpeed:26 withAgility:26 withLuck:12 withEndurance:10 withIntelligence:15 withElement:kFire withStatus:kStatusNormal];
}
return self;
}
and so in the game scene, can I just be like:
(in .h file)
PlayerCharacter *mainChar;
#property(retain)PlayerCharacter *mainChar;
(in .m file)
-(id) init
{
if((self=[super init]))
{
//the usual stuff
mainChar = [MainCharacter init];
return self;
}
}
However, I've seen online and in tutorials people using
MainCharacter *mainChar = [MainCharacter alloc];
would that be the same as
mainChar = [MainCharacter init];
if not could someone help clarify which syntax to use. Thanks very much :D Have a great day!
I think you should consider reading quickly through some introduction tutorials. This one is awesome and will get you used to the syntax and semantics of Objective-C:
http://cocoadevcentral.com/d/learn_objectivec/
alloc will allocate memory for an object and init will set things up like a regular constructor. You will also see initWith... style functions which can also be used like so:
MyObjectClass *instance = [[MyObjectClass alloc] init];
This then needs to be released in the same class it was created in the dealloc method.
As for setting up objects, it's better to not use a really long method name declarePlayer:Hero withLevel... but rather:
Set up the object and then alter properties:
Player *player = [[Player alloc] init];
player.health = 10;
player.armor = 20;
...
Once you become familiar with Objective-C as a language, tacking cocos2d and any other code will be much easier. For that, you can visit the the programming guide and find tutorials around the web like at www.learn-cocos2d.com.

Memory Management question

What is the best approach to release multiple instances of an object that you can't release right away?
For example, if you are creating a bunch of thumbnails:
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage]
//position the thumbs here, etc.
//assume releasing here breaks the app because we need to interact with the thumbs later
// [newThumb release] --- breaks the app
}
Would it make sense to put all the new objects in an array and release them all in viewDidUnload when we no longer need them?
Presumably you are adding each newThumb as a subview of some other view or to an array, so you should be fine to do that and then release newThumb here. For example:
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage];
[myThumbs addObject:newThumb];
[newThumb release];
This works becuase myThumbs retains the object.
In order not to leak the memory, especially if you regenerate the thumbnails, you would want to iterate over the superview's subviews (all the thumbs), remove each from the superview, and release them. You may also need to do this in you dealloc method where you release the superview (assuming you do that). With an array, you could simply call removeAllObjects, I believe.
Maybe I'm missing something, but why not use autoreleasepools?
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[[Thumb alloc] initWithImage:someImage]autorelease];
}
[pool drain];
Calling autorelease will add it to the pool (that you can create in any scope you like). Just call drain (or release) on the pool when you're done with it. This will release all queued objects.
You can release them right after adding to the array, because the array retains them:
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage]
//position the thumbs here, etc.
[thumbsArray addObject:newThumb];
[newThumb release]; // --- doesn't break the app
}
In viewDidUnload and/or dealloc release the array. You don't need to release every single thumb.
We should always avoid allocating memory in a loop. In your case you should release memory immediately after using the object you have created. i.e
for(int i=0; i <totalThumbs; i++){
Thumb *newThumb = [[Thumb alloc] initWithImage:someImage];
//position the thumbs here, etc.
//assume releasing here breaks the app because we need to interact with the thumbs later
// [newThumb release] --- breaks the app
// Work with newThumb
[newThumb release];
}
By doing this the objects get released each time loop runs. Actually each time the loop runs, a new object is created. This is how you can manage memory allocation in a loop.
Cheers!

releasing problems

Yes, I know that my problem is not the unique, but I have analysed many source codes but nothing works well.
I am creating an array of 100 images which will be used for UIImageView animation and after finishing animation I want to free the memory.
.h
IBOutlet UIImageView* 1mage;
.m
NSMutableArray* 1mageArray = [[NSMutableArray alloc]initWithCapacity:99];
for (int i=1; i<=100; i++) {
[1mageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"1mage_%.4d.png",i]]];
}
1mage.animationImages = 1mageArray;
1mage.animationDuration = 8.0;
1mage.animationRepeatCount = 0;
[1mage startAnimating];
[1mageArray release];
I have read Memory Management Guide, and seems, like I am doing everything okay, but debugger does not agree with me, and after playing the animation I still have 60 MB used.
Thanks in advance!
copy/paste , and I shorted variables by replacing the long text with 1. do not pay attention on this!
Your image will retain the data when you you set the animationImages. After all, it needs those images, doesn't it?
If you release it or set its animationImages property to nil, they might be released...

Instruments Living Count and/or Retain Count Issue

I'm running into a retain count issue that I do not understand. (I've added what I believe to be the retain count of vertex in [] at the end of each line of code).
CBVertex *vertex = nil;
for(int i=0; i<10; i++) {
vertex = [[CBVertex alloc] initWithFrame:CGRectMake(minX, y, 10.0, 10.0)]; // retain count [1]
[vertex setTag:i];
[vertex setAnimationDelegate:self];
[gameboard addSubview:vertex]; // retain count [2]
[tripGraph addVertex:vertex]; // retain count [3]
[vertex release]; vertex=nil; // retain count [2]
}
CBVertex is a subclass of UIView, gameboard is a UIView and tripGraph is a class that, among other things, has an NSMutableArray (privateVerticies) to which vertex is added to in its addVertex method.
After the above is executed, Instruments shows that there are 10 instances of CBVertex living.
Later in the code execution (I've confirmed that this code executes):
[[tripGraph verticies] makeObjectsPerformSelector:#selector(removeFromSuperview)];
// gameboard should have no references to any of the CBVertex's (correct??)
[tripGraph removeAllVerticies];
// tripGraph privateVerticies is empty and no references to any of
// the CBVertex's (correct?)
Relevant tripGraph methods:
-(NSArray *) verticies {
return [NSArray arrayWithArray:privateVerticies];
}
-(void) tripGraph removeAllVerticies {
[privateVerticies removeAllObjects];
}
- (void) addVertex:(CBVertex *)vertex {
[privateVerticies addObject:vertex];
}
The issue arises when the second set of CBVertex's are created. Instruments shows that the first set of CBVertex's is still live (i.e. the number of instances of CBVertexs is now 20).
I'm (obviously?) missing a release somewhere, but don't understand where . . .
Help/pointers are appreciated!!
thanks
tom
If gameboard and tripGraph are still retaining that CBVertex object, then even if you release it in your loop, it will continue to exist until you remove the CBVertex object from gameboard and tripGraph as well.
OK, I'm closing this out as the code above is correct. The problem lies in my overriden removeFromSuperview method in which there is an animation going on. I'm going to investigate further and if I don't have it figured out, I'll repost a new question. (and link to it from here).
Thanks for the comments, answer and several views.
For those interested, here's what was going on and how it was resolved.
In CBVertex, I've overridden removeFromSuperview. In that overridden method, I am animating the view's layer and setting the view as the CAAnimations delegate (which is retained by the CAAnimation) and calling super removeFromSuperview. The animation is not removed on completion.
As the animation retains the delegate and the animation is not removed, the view's retain count remains at +1.
My resolution was to create an intermediate method to perform the animation. When the animation is complete it calls the overriden removeFromSuperview, which now only removes all animations and calls super. Removing the animation releases it, which in turn releases it's reference to it's delegate (the CBVertex) and the CBVertex's retain count goes to +0.
One final thought for anyone chasing retain counts: don't think of them as absolute values. The inner workings of the objects you may be using might be retaining your instances more than you'd expect -- think of retain counts as a delta.
SomeClass *myObject = [[SomeClass alloc] init]; // retain count +1
[someMutableSet addObject:myObject]; // retain count +2

Problem adding multiple annotations to map

Ok, so I’m having this problem. What I want to do is manually add multiple annotations to a map. When I add just one annotation, it works flawlessly. The pin drops, you can click on it to see its callout, life is good.
The problem comes when I want to add more than one. When I add the second, suddenly the pin’s aren’t coloured correctly (i.e. depending on their magnitude they should be a certain color, but they’re now both the same…), and more importantly when you click on them, to see their callout, the app crashes with exex_bad_access. I really have no idea what’s wrong, maybe I’m adding too many views to the map? But it’s only 9 pins and the pins themselves add just fine.
Here’s my code…
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *stops = [[NSMutableArray alloc] init]; //Get list of all the stops available
Bus *bus1 = [[Bus alloc] init]; // Bus 1 holds the stops
stops = [bus1 returnStops];
for (NSString *stop in stops) //Go through each stop to add annotation to map
{
Bus *bus2 = [bus1 initWithStop:stop]; //Create an instance of bus with a given stop
MapAnnotation *eqAnn = [MapAnnotation annotationWithBus:bus2];
[self.mapView addAnnotation:eqAnn]; //Add the annotation to the map
//[eqAnn release];
//[bus2 release];
}
[self recenterMap];
[stops release];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *view = nil;
if(annotation != mapView.userLocation) {
MapAnnotation *eqAnn = (MapAnnotation*)annotation;
view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"busLoc"];
if(nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:eqAnn
reuseIdentifier:#"busLoc"] autorelease];
}
CGFloat magnituide = [eqAnn.bus.magnitude floatValue];
if(magnituide >= .80f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorRed];
} else if(magnituide >= .60f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorPurple];
} else
{
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorGreen];
}
[(MKPinAnnotationView *)view setAnimatesDrop:YES];
[view setCanShowCallout:YES];
}
return view;
}
even tried removing the second function, but it didn’t do anything.
Thanks for the help!
P.S I should also add, there’s usually one or two pins out of the 9 which works when you click the annotation…
If i even try to manually just two annotations by hand in the program (i.e., remove the loop), it still fails and the color is still wrong...
It would appear that your memory management of the stops variable is incorrect. You allocate a mutable array, then replace that array with the return value of -[Bus returnStops], then release that. Also it's not clear what's going on with bus2 - does -[Bus initWithStop:] return a different instance of Bus? It's not usual to send any method -init* on an already-initialised object. I think that you probably are confused by the memory management conventions in Cocoa Touch. Here's a collection of articles and other references on Cocoa memory management (which is the same beast).
Have you tried using AddAnnotations instead of add annotation? - (void)addAnnotations:(NSArray *)annotations. This might work for you...but looking at the answer above and further inspection you are having some memory managment issues in your viewDidLoad (though thi s might not be the cause of your problem, but it could be). First of you are allocating the array (stops) and then ovveriding it with some array in the Bus object, this will cause a leak. Also you are then releasing that array which might be causing the crash since you are releasing the array that is actually in the Bus object w ithout having increased a reference count to it. I am not sure what initWithStop is doing but you might be getting a leak here too if initWithStop retains the object.
I wouldn't call it a memory management problem -- I'd just say you are using array references incorrectly.
After constructing the array with NSMutableArray *stops = [[NSMutableArray alloc] init], the next step is to use [stops addObject: ] to add each stop you want to store.
After that? It's not clear what you are really trying to do.
SO the answer was that I kept sending bus1 the init object, so it got confused.
"Hi David,
Your data model looks hosed to me. You only have one bus object that you are repeatedly sending initWithStop: to.
Hope this helps.
Good luck!
"
Thank you guys for your help! You all helped me quite a bit!