I'm a beginner level programmer trying to make a game app for the iphone and I've encountered a possible issue with the memory management (exc_bad_access) of my program so far. I've searched and read dozens of articles regarding memory management (including apple's docs) but I still can't figure out what exactly is wrong with my codes. So I would really appreciate it if someone can help clear up the mess I made for myself.
//in the .h file
#property(nonatomic,retain) NSMutableArray *fencePoleArray;
#property(nonatomic,retain) NSMutableArray *fencePoleImageArray;
#property(nonatomic,retain) NSMutableArray *fenceImageArray;
//in the .m file
- (void)viewDidLoad {
[super viewDidLoad];
self.gameState = gameStatePaused;
fencePoleArray = [[NSMutableArray alloc] init];
fencePoleImageArray = [[NSMutableArray alloc] init];
fenceImageArray = [[NSMutableArray alloc] init];
mainField = CGRectMake(10, 35, 310, 340);
..........
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(gameLoop) userInfo:nil repeats:YES];
}
So basically, the player touches the screen to set up the fences/poles
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(.......) {
.......
}
else {
UITouch *touch = [[event allTouches] anyObject];
currentTapLoc = [touch locationInView:touch.view];
NSLog(#"%i, %i", (int)currentTapLoc.x, (int)currentTapLoc.y);
if(CGRectContainsPoint(mainField, currentTapLoc)) {
if([self checkFence]) {
onFencePole++;
//this 3 set functions adds their respective objects into the 3 NSMutableArrays using addObject:
[self setFencePole];
[self setFenceImage];
[self setFencePoleImage];
.......
}
}
else {
.......
}
}
}
}
The setFence function (setFenceImage and setFencePoleImage is similar to this)
-(void)setFencePole {
Fence *fencePole;
if (!elecFence) {
fencePole = [[Fence alloc] initFence:onFencePole fenceType:1 fencePos:currentTapLoc];
}
else {
fencePole = [[Fence alloc] initFence:onFencePole fenceType:2 fencePos:currentTapLoc];
}
[fencePoleArray addObject:fencePole];
[fencePole release];
and whenever I press a button in the game, endOpenState is called to clear away all the extra images(fence/poles) on the screen and also to remove all existing objects in the 3 NSMutableArray. Point is to remove all the objects in the NSMutableArrays but keep the array itself so it can be reused later.
-(void)endOpenState {
........
int xMax = [fencePoleArray count];
int yMax = [fenceImageArray count];
for (int x = 0; x < xMax; x++) {
[[fencePoleImageArray objectAtIndex:x] removeFromSuperview];
}
for (int y = 0; y < yMax; y++) {
[[fenceImageArray objectAtIndex:y] removeFromSuperview];
}
[fencePoleArray removeAllObjects];
[fencePoleImageArray removeAllObjects];
[fenceImageArray removeAllObjects];
........
}
The crash happens here at the checkFence function.
-(BOOL)checkFence {
if (onFencePole == 0) {
return YES;
}
else if (onFencePole >= 1 && onFencePole < currentMaxFencePole - 1) {
CGPoint tempPoint1 = currentTapLoc;
CGPoint tempPoint2 = [[fencePoleArray objectAtIndex:onFencePole-1] returnPos]; // the crash happens at this line
if ([self checkDistance:tempPoint1 point2:tempPoint2]) {
return YES;
}
else {
return NO;
}
}
else if (onFencePole == currentMaxFencePole - 1) {
......
}
else {
return NO;
}
}
So the problem here is, everything works fine until checkFence is called the 2nd time after endOpenState is called. So its like tap_screen -> tap_screen -> press_button_to_call_endOpenState -> tap screen -> tap_screen -> crash
What I'm thinking of is that fencePoleArray got messed up when I used [fencePoleArray removeAllObjects] because it doesn't crash when I comment it out. It would really be great if someone can explain to me what went wrong. And thanks in advance.
First, a couple of suggestions:
if (!elecFence) {
fencePole = [[Fence alloc] initFence:onFencePole
fenceType:1 fencePos:currentTapLoc];
}
else {
fencePole = [[Fence alloc] initFence:onFencePole
fenceType:2 fencePos:currentTapLoc];
}
You’re making this too hard, how about this:
const int fenceType = elecFence ? 2 : 1;
Fence *fencePole = [[Fence alloc] initFence:onFencePole
fenceType:fenceType fencePos:currentTapLoc];
And this:
int xMax = [fencePoleArray count];
int yMax = [fenceImageArray count];
for (int x = 0; x < xMax; x++) {
[[fencePoleImageArray objectAtIndex:x] removeFromSuperview];
}
for (int y = 0; y < yMax; y++) {
[[fenceImageArray objectAtIndex:y] removeFromSuperview];
}
Could be shortened using makeObjectsPerformSelector:
const SEL remove = #selector(removeFromSuperview);
[fencePoleImageArray makeObjectsPerformSelector:remove];
[fenceImageArray makeObjectsPerformSelector:remove];
This is shorter and safer, as the xMax bound in your code is computed from fencePoleArray and used to iterate over fencePoleImageArray. (Could be right, could be wrong.)
Now to the objectAtIndex: call. If the array is still in memory and you tried to access an object beyond the array bounds, you would get an exception. So that I guess that either the array or some of the objects in it got released without you knowing it. You could try to NSLog the array and the object on given index and try to log their retainCount. If the logging line crashes, you have found the object that’s been released and can start looking for the cause.
(And one more thing: You should split the game logic into a separate model class. This simplifies the code and makes it easier to reason about.)
If you want to use properties, you should use self.propertyName = ... instead of propertyName = ....
Hope this will help.
Related
In my .h file, I have:
#property (nonatomic) NSMutableArray *cards;
In my .m file, I have, in the initializer:
- (id) init
{
self = [super init];
self.cards = [NSMutableArray alloc];
return self;
}
In a loop that populates a number of items visible and on-screen:
[self.cards addObject:noteView];
And in a touch event handler, I have:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"In touchesBegan.");
UITouch *touch = [touches anyObject];
UIView *selectedView = nil;
CGPoint touchLocation = [touch locationInView:self.view];
for (UIView *card in _cards)
{
CGRect cardRect = [card frame];
NSLog(#"%f", cardRect.origin.x);
NSLog(#"%f", cardRect.origin.y);
NSLog(#"%f", cardRect.size.height);
NSLog(#"%f", cardRect.size.width);
if (CGRectContainsPoint(cardRect, touchLocation)) {
NSLog(#"Match found.");
selectedView = card;
CGRect selectedFrame = selectedView.frame;
selectedFrame.origin.y = -selectedFrame.size.height;
selectedFrame.size = selectedView.frame.size;
float heightRatio = (float) floor([[UIScreen mainScreen] bounds].size.height + .4) / (float) selectedFrame.size.height;
float widthRatio = (float) floor([[UIScreen mainScreen] bounds].size.width + .4) / (float) selectedFrame.size.width;
float ratio = MIN(heightRatio, widthRatio);
selectedFrame.size.height *= ratio;
selectedFrame.size.width *= ratio;
selectedFrame.origin.x = -selectedFrame.origin.x * ratio;
}
}
}
The output for every touch I've made has been that the unconditional NSLog statement is output, but none of the "Log this float" statements execute. It seems that I have not correctly initialized or not correctly populated NSMutableArray.
I seem to get the same behavior whether I refer to _cards or self.cards.
Thanks for any help,
--EDIT--
I seem to be sticking on something else than originally thought of. I now have self.cards = [[NSMutableArray alloc] init], but identical behavior: I go through a loop and populate a bunch of cards, but when I tap one of them, the touch handler outputs "In touchesBegan." but none of the floats. Given an updated init, why would touchesBegan be acting as if it hadn't seen any cards after a number of cards are visible onscreen? (The other output should give a number of lines of floats whether or not the touch was on target for a particular card.)
You need to init the array!
self.cards = [[NSMutableArray alloc] init];
Or
self.cards = [NSMutableArray new];
They are both equivalents
With alloc the only thing you are doing is reserve space in the memory for that variable.
From Apple Documentation:
alloc
Returns a new instance of the receiving class.
init
Implemented by subclasses to initialize a new object (the receiver) immediately after memory for it has been allocated.
Are you using a storyboard for this? Try to initialize your cards property in viewDidLoad.
As a quick check, try to initialize that property right before the loop that is adding objects to it.
I'm following the Stanford course, and we had to build a method for the app that checks for 2 cards matching, this is how the model that have the logic looks like (the method to look there is flipCardAtIndex):
#import "CardMatchingGame.h"
#import "PlayingCardsDeck.h"
#interface CardMatchingGame()
#property (readwrite, nonatomic) int score;
#property (strong, nonatomic) NSMutableArray *cards;
#property (strong, nonatomic) NSString *notification;
#end
#implementation CardMatchingGame
-(NSMutableArray *) cards {
if (!_cards) _cards = [[NSMutableArray alloc] init];
return _cards;
}
-(id)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck {
self = [super init];
if (self) {
for (int i = 0; i < count; i++) {
Card *card = [deck drawRandonCard];
if (!card) {
self = nil;
} else {
self.cards[i] = card;
}
}
}
return self;
}
-(Card *) cardAtIndex:(NSUInteger)index {
return (index < self.cards.count) ? self.cards[index] : nil;
}
#define FLIP_COST 1
#define MISMATCH_PENALTY 2
#define BONUS 4
-(void) flipCardAtIndex:(NSUInteger)index {
Card *card = [self cardAtIndex:index];
if (!card.isUnplayable) {
if (!card.isFaceUp) {
for (Card *otherCard in self.cards) {
if (otherCard.isFaceUp && !otherCard.isUnplayable) {
int matchScore = [card match:#[otherCard]];
if (matchScore) {
otherCard.unplayble = YES;
card.unplayble = YES;
self.notification = [NSString stringWithFormat:#"%# & %# match!", card.contents, otherCard.contents];
self.score += matchScore * BONUS;
} else {
otherCard.faceUp = NO;
self.score -= MISMATCH_PENALTY;
self.notification = [NSString stringWithFormat:#"%# did not matched to %#", card.contents, otherCard.contents];
}
break;
}
}
self.score -= FLIP_COST;
}
card.faceUp = !card.isFaceUp;
}
}
#end
And this is the class model of the whole game, that got the actual matching method:
#import "PlayingCards.h"
#implementation PlayingCards
#synthesize suit = _suit;
//overriding the :match method of cards to give different acore if its only a suit match or a number match
-(int)match:(NSArray *)cardToMatch {
int score = 0;
if (cardToMatch.count == 1) {
PlayingCards *aCard = [cardToMatch lastObject];
if ([aCard.suit isEqualToString: self.suit]) {
score = 1;
} else if (aCard.rank == self.rank) {
score = 4;
}
}
return score;
}
//more stuff...
W already created it with an array so we will be able to extend it for more objects, but now i'm trying to figure out how do I extend it :/
This is my github for the project https://github.com/NirOhayon/Matchismo
i'm new to objective C and would appreciate it munch if you could help me to figure it out.
Thanks a bunch
You can chain these with a loop to check them all. Very basic way of doing it. Just loop through each card and check it against the "self" card that you have and increment the score instead of setting it.
-(int)match:(NSArray *)cardToMatch {
int score = 0;
for(int i = 0; i < cardToMatch.count; i++) {
PlayingCards *aCard = cardToMatch[i];
if ([aCard.suit isEqualToString: self.suit]) {
score += 1;
} else if (aCard.rank == self.rank) {
score += 4;
}
}
return score;
}
For flipCardAtIndex: , I would change it to flipCardsAtIndexes:(NSArray*)indexes, where indexes is an NSArray of NSNumbers. Then I would run a for loop checking and removing any cards that are unplayable or faceup, and pass through the remaining cards at those indexes to check match, and retrieve the match score.
To tell your view controller to add another card depends on how you have your view controller set up. You could do a protocol in a method that your view controller becomes the delegate of, and through a protocol method, tell it to switch. It could also be simpler than that, depending on how it checks your model of cards to decide what to show, if it sees three cards available instead of two, it could switch.
As the point of this exercise is to learn iOS programming, I want to give you a good head start and you should tweak and figure some stuff out on your own. I have a feeling you're a novice at programming, and if you are, you'll be surprised how much programming at your stage is trial and error. Eventually it will become second nature.
I want to make NSMutableArray to store the touch time gap, but came across a problem:
The NSMutableArray's count is only 1.
What can I do?
NSInteger count = 0;
float firstTempTime = 0;
float nTempTime = 0;
float nTouchTimegap = 0;
#pragma mark –
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
messageLabel.text = #"Touches Began";
[self updateLabelsFromTouches:touches];
NSArray *array = [touches allObjects];
for (UITouch *touch in array){
count = count +1;
NSLog(#"began touch count: %d", count);
nTempTime = [touch timestamp];
NSLog(#"n TempTime stamp : %lf", nTempTime);
if (count == 1) {
firstTempTime = [touch timestamp];
}
else {
nTouchTimegap = nTempTime - firstTempTime;
firstTempTime = nTempTime;
}
NSLog(#"nTouchTimegap : %lf", nTouchTimegap);
nTouchTimegapArray = [NSMutableArray arrayWithCapacity:count];
[nTouchTimegapArray addObject:[NSNumber numberWithFloat: nTouchTimegap]];
for(id obj in nTouchTimegapArray){
NSLog(#"nTouchTimegapArray : %i", [nTouchTimegapArray count]);
NSLog(#"nTouchTimegapArray : %#", obj);
}
}
touchesBegan: will be called once you touch the screen, if you touch the screen with a second finger it will be called again.
You should take a look at touchesEnded: and touchesCancelled:
I guess I have to spell it out:
nTouchTimegapArray = [NSMutableArray arrayWithCapacity:count];
[nTouchTimegapArray addObject:[NSNumber numberWithFloat: nTouchTimegap]];
That sequence effectively erases the array on each iteration. There's no possible way to ever have more than one entry in the array.
Move the first line out of the loop, and, if you want to accumulate touches from all invocations of the method, move it to the init routine or viewDidLoad or some such.
I have the following method in my app :
- (void) loadModel {
// Runs in a seperate thread so we need to create an additional NSAutoreleasePool
pool = [[NSAutoreleasePool alloc] init];
NSData *getData = [activeModelInfo getFileData];
if (getData) {
if ([activeModel loadFromFileData:daeData]) {
[activeModel reset];
[mainViewController performSelectorOnMainThread:#selector(showModelView) withObject:nil waitUntilDone:NO];
}
else {
}
}
else {
}
[pool release];
}
The loadFromFileData method calls the following code which loads data. :
- (void) loadVerticesFromSource:(NSString*)verticesSourceId meshNode:(TBXMLElement*)meshNode intoMesh: (Mesh3D*) mesh {
NSLog(#"Getting Vertices for Source %#",verticesSourceId);
FloatArray *floatArray = [self getFloatArrayFromSource: verticesSourceId meshNode: meshNode];
if (floatArray) {
[daeFloatArray addObject:floatArray];
}
if (floatArray) {
if ([floatArray.array count] % 3 != 0) {
NSLog(#"Float array length not divisible by 3!");
}
else {
mesh->verticesCount = [floatArray.array count] / 3;
mesh->vertices = malloc(sizeof(Vector3D) * mesh->verticesCount);
for (int i=0; i<mesh->verticesCount; i++) {
mesh->vertices[i].x = [[floatArray.array objectAtIndex:(i*3)] floatValue];
mesh->vertices[i].y = [[floatArray.array objectAtIndex:(i*3)+1] floatValue];
mesh->vertices[i].z = [[floatArray.array objectAtIndex:(i*3)+2] floatValue];
// update extents information
if (!extents.pointDefined || mesh->vertices[i].x < extents.minX) extents.minX = mesh->vertices[i].x;
else if (!extents.pointDefined || mesh->vertices[i].x > extents.maxX) extents.maxX = mesh->vertices[i].x;
if (!extents.pointDefined || mesh->vertices[i].y < extents.minY) extents.minY = mesh->vertices[i].y;
else if (!extents.pointDefined || mesh->vertices[i].y > extents.maxY) extents.maxY = mesh->vertices[i].y;
if (!extents.pointDefined || mesh->vertices[i].z < extents.minZ) extents.minZ = mesh->vertices[i].z;
else if (!extents.pointDefined || mesh->vertices[i].z > extents.maxZ) extents.maxZ = mesh->vertices[i].z;
if (!extents.pointDefined) extents.pointDefined = YES;
[pointerStorageArray addObject:[NSValue valueWithPointer:mesh->vertices]];
}
}
}
}
Since this method is called several times while the data loads, each time mallocing memory for the mesh->vertices struct, I have created a pointerStorage array where I store the pointer to the malloced memory.
The app then displays a 3D object using OpenGL ES. When the user presses a Main Menu button, I then free up the pointerStorageArray as follows :
- (void) freeUpMallocedMemory
for (NSValue * value in pointerStorageArray) {
free(value);
}
The problem is that the app then crashes during this process. All I get is the EXC_BAD_ACCESSS error message and a pointer to the following line of code in the loadModel method above :
[pool release];
Nothing in the stack trace. I have also tried turning on NSZombie, NSAutoreleaseTrackFreedObjectCheck and NSDebugEnabled, but I still don't get any additional information.
The odd thing is that if I put a delay on the button of 2 seconds (i.e only trigger the freeUpMallocedMemory method after 2 seconds), the app no longer crashes and works fine.
Can anyone suggest what might be causing this - really confused and have already spent a few days troubleshooting.
Thank you !
You are over-releasing... the values in pointerStorageArray are created via a convenience method and as such don't need releasing, simply removing them from the array will dispose of them.
I'm making a little game, here is some example code of whats going on:
-(id) init
{
self.arrowProjectileArray = [[[NSMutableArray alloc] init] autorelease];
self.batchNode = [CCSpriteBatchNode batchNodeWithTexture:[[CCTextureCache sharedTextureCache] addImage:#"arrow.png"]];
[self addChild:_batchNode z:2];
for (CCSprite *projectile in _arrowProjectileArray) {
[_batchNode removeChild:projectile cleanup:YES];
}
[_arrowProjectileArray removeAllObjects];
self.nextProjectile = nil;
}
}
-(void) callEveryFrame:(ccTime)dt{
for (int i = 0; i < [_arrowProjectileArray count];i++) {
CCSprite *cursprite = [_arrowProjectileArray objectAtIndex:i];
if (cursprite.tag == 1) {
float x = theSpot.x+10;
float y = theSpot.y+10;
cursprite.position = ccp(x, y);
}
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_batchNode addChild:_nextProjectile z:1 tag:1];
[_arrowProjectileArray addObject: _nextProjectile];
[self spriteMoveFinished];
}
-(void) dealloc
{
self.arrowProjectileArray = nil;
self.nextProjectile = nil;
[super dealloc];
}
The only code that I included was code that is relevant to the arrow's projection.
The arrow shoots fine, the problem is every time I shoot the stupid thing, I think it shoots a new arrow, but puts multiple arrows onto of that 1 arrow and makes it look like a fat ugly arrow pixel thing. What am I doing wrong? I'm not too familiar with NSMutableArray, but I'm currently stuck.
In init method, you create a new NSMutableArray instance and assign it to self.arrowProjectileArray, then you traverse the arrowProjectileArray in the following lines using a for loop. If addChild: method does not add anything to arrowProjectileArray, then your code has a logic mistake, because what you do by traversing arrowProjectileArray is traversing an empty array, which means you do nothing in that code.
You should double-check what you intend to do and what your code is doing actually.
I solved my own problem by doing a little bit of research, I also got rid of the batch node.