NSArray possibly causing app crash? - crash

I have a method that chooses a random sprite node from an array to place on the screen. My app is crashing though. This is a snippet from the error message:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString decodeObjectForKey:]: unrecognized selector sent to instance 0x47b4'
This is the method I have written out.
-(SKSpriteNode *) gamePieces {
NSArray *things = [NSArray arrayWithObjects: #"piece1", #"piece2", #"piece3", nil];
int r = arc4random () % [things count];
SKSpriteNode *randomObject = [[SKSpriteNode alloc] initWithCoder:[things objectAtIndex:r]];
return randomObject;
}
Any ideas as to what the problem is?

As already mentioned by #HotLicks and #LearnCocos2d, -initWithCoder: is out of place here. Creating a new SKSpriteNode from scratch is typically done with one of the designated initializers (+spriteNodeWithColor:size:,+spriteNodeWithImageNamed: or spriteNodeWithTexture:). Assuming you just wish to create an SKSpriteNode with a random image, take a look at the following code.
- (SKSpriteNode *) randomGamePiece {
NSArray *filename = [NSArray arrayWithObjects: #"piece1", #"piece2", #"piece3", nil];
int r = arc4random () % [things count];
NSString * randomFilename = [things objectAtIndex:r];
SKSpriteNode *randomObject = [SKSpriteNode spriteWithImageNamed:randomFilename];
return randomObject;
}
I wholeheartedly recommend browsing through the Sprite Kit Programming Guide which contains many useful snippets and pointers for new and intermediate Sprite Kit programmers.

Related

EXC_BAD_ACCESS on NSString sizeWithFont

I am not sure what is going on anymore. I have tried so many things to for me to figure out whats going on on this specific line of code that is causing a EXC_BAD_ACCESS.
I tried enabled NSZombies but it didn't help me any. Here is the code:
- (int)linesFromText:(NSString *)string withFont:(UIFont *)font andSize:(CGSize)size {
NSArray *splitString = [string componentsSeparatedByString:#" "];
NSMutableArray *allLines = [NSMutableArray array];
NSMutableString *line = [NSMutableString string];
NSString *word;
NSString *fakeLine;
for (int i = 0; i < splitString.count; i++) {
word = [splitString objectAtIndex:i];
fakeLine = [NSString stringWithFormat:#"%#%# ",line, word];
//NSLog(#"line %#, font %#",fakeLine,font);
////THIS IS THE LINE CAUSING THE EXC_BAD_ACCESS
CGSize lineSize = [fakeLine sizeWithFont:font];
if (lineSize.width <= size.width) {
[line appendFormat:#"%# ", word];
} else {
[allLines addObject:[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
line = [NSMutableString string];
[line appendFormat:#"%# ", word];
}
}
[allLines addObject:[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
return allLines.count;
}
This is driving me crazy cause it only happens on the new iphone 5 when you scroll through the days to fast in the app I built. Here is a link to the application in the store:
http://itunes.apple.com/us/app/id543324451?mt=8
if you have an iphone 5 you can see what I mean. iphone 4 doesn't do this.
This code is being called in a UITableViewCell's layoutSubviews and is there to help size the frame of a Custom Attributed label that uses TTTAttributedLabel (https://github.com/mattt/TTTAttributedLabel).
I also tried to enable "Gaurd malloc to try give me more details but my XCode gives me this error:
dyld: could not load inserted library '/usr/lib/libgmalloc.dylib' because image not found
which if I look in /usr/lib that file is a sym link to a file that does exist in the same directory:
libgmalloc.dylib -> libgmalloc.B.dylib
I am running out of ideas here and thought it might be the UIFont getting released to soon and then it not being available so I put references in the UITableViewCell to hold the UIFont until the end of that Cell's life.
I have also searched around the internet and haven't found that much on this specifics.
Also here is an image of my stack trace from debugger:
http://i.stack.imgur.com/gWC5L.png
Any ideas?
Did I provide enough info?
Thanks
I think your question is answered here:
UIStringDrawing methods don't seem to be thread safe in iOS 6
The short version: sizeWithFont, and most other UIKit methods, are not thread safe when you're using it on the screen (rather than using it to pre-render).
The good news: look at Adam Swinden's answer in that thread; he explains how to get the same result in iOS6 with CoreText instead of UIKit.

Analyzer claiming an object was released when it wasn't

I am getting a static analysis error in this code which doesn't make any sense to me. The error is:
Reference-counted object is used after it is released
This is glue code to allow for PNG loading in a game originally written in C++.
int pngLoad(const char *filename, pngInfo *info, int format, GLuint *textureName)
{
char fullPath[Engine::Settings::MaxPath];
strcpy(fullPath, filename);
appendAppBundlePath(fullPath);
NSString *path = [NSString stringWithCString:fullPath encoding:NSUTF8StringEncoding];
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
UIImage *image = [[UIImage alloc] initWithData:data];
[data release];
Texture2D *loadedTex = [Texture2D alloc];
// ##### Analyzer claims the object is released here: #####
[loadedTex initWithImage:image format:format];
int didLoad;
// ##### Error is here: #####
if (loadedTex.contentSize.width == 0 || loadedTex.contentSize.height == 0)
{
didLoad = 0;
}
else
{
didLoad = 1;
*textureName = loadedTex.name;
// return texture info
info->ScaleFactor = loadedTex.scaleFactor;
info->Width = (float)image.size.width / (float)info->ScaleFactor;
info->Height = (float)image.size.height / (float)info->ScaleFactor;
info->Alpha = 1;
info->PaddedWidth = loadedTex.pixelsWide;
info->PaddedHeight = loadedTex.pixelsHigh;
}
[loadedTex release];
[image release];
return didLoad;
}
If I use Texture2D *loadedTex = [[Texture2D alloc] retain]; this warning is removed, but then an warning that I've leaked an object comes up, so something is seriously weird here.
initWithImage:format: used to contain a [self release] which shouldn't have been there, which I removed when I found this warning. However, even after a full clean and rebuild, I still get the warning. Am I doing something else wrong? Is something not getting properly cleaned up by the Clean command in Xcode?
The analyzer may be right, at least in a general way.
Texture2D *loadedTex = [Texture2D alloc];
[loadedTex initWithImage:image format:format];
In general, "init" might actually discard the object passed in and return a different one. Whether or not this is the case for "Texture2D" is something I don't know, but if the analyzer is going for the general case then it is right.
You should be able to work around that by using
Texture2D *loadedTex = [Texture2D alloc];
loadedTex=[loadedTex initWithImage:image format:format];
Or by simply combining the two calls, as it is done in most Objective-C examples.
You should always combine the alloc and initXXX call when creating objects, in this case
Texture2D *loadedTex = [[Texture2D alloc] initWithImage:image format:format];
An init method need not return the same object it was called with, it is allowed to return a different object.
In this case your result from loadedTex = [Texture2D alloc] would be released and initWithImage would return a different object (which you discard).

Cocos2d adding children in a loop : Child cannot be added

I am quite new to Objective-C and Cocos2d, and i don't really understand why i am getting the following error with the following code. Could you please help ?
- (NSArray *) menuItemsArray
{
NSMutableArray *array = [NSMutableArray alloc];
for (Player * player in playersArray) {
NSLog(#"creating menu item with image %#",player.imageFilename);
CCLayer *newPage = [[CCLayer alloc] init];
CCMenuItemImage *titem = [CCMenuItemImage itemFromNormalImage:player.imageFilename
selectedImage:player.imageFilename
target:self selector:#selector(playerSelected:)];
CCMenu *menu = [CCMenu menuWithItems: titem, nil];
menu.position = ccp(self.contentSize.width/2, self.contentSize.height/2);
NSLog(#"before adding item to layer");
//[titem removeFromParentWithCleanup:YES];
[newPage addChild:titem];
//[newPage release];
NSLog(#"after adding item to layer");
[array addObject:newPage];
/*
[titem release];*/
//[newPage release];
//[menu release];
}
return array;
}
and i get the error:
2012-06-15 17:13:11.003 Killer[3222:707] *** Assertion failure in -[CCLayer addChild:z:tag:], /Users/max/Documents/workspace/Killer/Killer/libs/cocos2d/CCNode.m:388
2012-06-15 17:13:11.006 Killer[3222:707] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'child already added. It can't be added again'
Sorry if this question is very low level... and thanks if you can help me ! :)
Your error is that you are trying to add created menuItem to two parents. add it to the menu only. Then add menu to the layer. Replace your line
[newPage addChild:titem];
with this one
[newPage addChild:menu];
and you will receive an array of pages for every player.

EXC_BAD_ACCESS when switching CCAnimations

I'm new to using CCAnimations in cocos2D, and I've run into a problem that I've had a hard time solving.
I'm making a basic platforming game, and the player sprite has various animations that need to be run depending on the state of the player.
I have the following code in my layer's init method:
sprite = [CCSprite spriteWithSpriteFrameName:#"stand.png"];
standingSprites = [NSArray arrayWithObjects:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"stand.png"],
nil];
runningSprites = [NSArray arrayWithObjects:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"run1.png"],
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"run2.png"],
nil];
standingAnimation = [CCAnimation animationWithFrames:standingSprites delay:0.2f];
runningAnimation = [CCAnimation animationWithFrames:runningSprites delay:0.2f];
animationAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:standingAnimation]];
[sprite runAction:animationAction];
This works as expected with either of the two animations. However, I want to run standingAnimation when the player is standing still and runningAnimation when the player is running. I attempted to do this as follows:
-(void)walk {
if(!isWalking) {
isWalking = true;
[sprite stopAction:animationAction];
animationAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:runningAnimation]];
[sprite runAction:animationAction];
}
}
The second to the last line crashes the program, causing an EXC_BAD_ACCESS (on referencing 0x0). Stepping through walk in the debugger, it doesn't appear that any of the relevant pointers are null.
From the stack trace:
2012-06-03 10:59:59.907 ThirtyMinutes[9876:6403] *** Terminating app
due to uncaught exception 'NSInvalidArgumentException', reason:
'-[NSCTFontDescriptor frames]: unrecognized selector sent to instance
0x7f808d93e9f0'
0x7f808d93e9f0 is the address of runningAnimation.
What am I doing wrong?
Is there a better way of doing this?
Thanks!
The error message is telling you that, at the moment of the error, 0x7f808d93e9f0 is the address of a NSCTFontDescriptor object. The likely reason is that you're not retaining runningAnimation and its memory has been reclaimed for a different object.
(Show the declaration of runningAnimation if its not obvious how that's happening.)
You have to recreate actions every time you want to use them. In your case, you tried to use action after it has been deallocated.

Basic IOS OOP - structure and implementation

I am creating a basic guessing game in iOS for my kids, and I think there are some fundamental gaps in my understanding of how I should be creating and releasing objects throughout the lifecycle of the app. I have been reading up on retain and release cycles but I think my issue is more to do with the fundamental architecture of the app and how I may be poorly trying to instantiate and then kill a few key objects of the app.
The problem centers around two specific classes.
I have a game class, which I have designed to hold all the information that the game requires to run. When it is init-ed, it holds all instance variables that point to arrays that hold strings such as the various clues, etc. It's basically a container for all the data that the game requires.
I have a game view controller, that creates and an instance of the game class and queries it so as to present on screen the various elements contained with the game object.
This works perfectly the fine. When the user starts a new game, a new instance of the game class is allocated and init-ed and away they go.
The issue comes in when I come to generate a new game. This happens a number of ways. Either The user finishes the game and starts another one or the user quits the current game and then starts a new one.
In my thinking, I would just release the game object and alloc and init a new one. However, I notice running on the device and looking through the profiler, that the game object isn't released at all.It's still, there and each instantiation of the game creates a new game object with the old one still sitting there with no pointers to it.
Fiddling around with the code, I noticed that I did not implement a dealloc method in the Game class...but when I try to do that, the app crashes, I suspect because I am trying to release a previously released object.
Ideally what I am trying to do is get rid of the old Game object, or replace the old one (overwrite) with a new one each time a new game is started.
However, is this approach wrong? Should I be doing it a completely different way? Such as only ever creating a single instance of the game class and rewriting a method inside that class so as to generate a new set of clues, etc everytime a new game starts and the GameViewController tells it to?
Is there a 'best practice' way to do this?
So you've got an idea of what I am doing, code is below for the GameViewController, where an instance of the Game class is created:
#import "GameViewController.h"
#implementation GameViewController
#synthesize game = _game;
-(void)startNewGameOfLevel:(NSInteger)level
{
if(!_game)
{
Game *g = [[Game alloc]initGamewithLevel:level];
[self setGame:g];
[g release]; g = nil;
}
[self set_currentlevel:[_game _currentLevel]];
// set up popover to show the rounds goal letter
[self setUpPopOver];
}
-(void)quitTheCurrentGameAndStartNewGame
{
[_game release]; _game = nil;
[self clearGamePlayingField];
animationStepIndex = 0;
[self startNewGameOfLevel: _currentlevel];
}
Game class (abridged) with the designated initializer of the Game class:
#import "Game.h"
#implementation Game
#synthesize arrayOfLowerCaseLetters = _arrayOfLowerCaseLetters;
#synthesize arrayOfPhrases= _arrayOfPhrases;
#synthesize goalLetter = _goalLetter;
#synthesize goalPhrase = _goalPhrase;
#synthesize gameLetterPool = _gameLetterPool;
#synthesize _indexForGoalLetter, _numberOfLevelsInGame, _currentLevel, _numberOfWhackHoles, _numberOfLettersInGameLetterPool;
-(id)initGamewithLevel:(NSInteger)level
{
[super init];
//create an array of lower case letters. These will
//contain the full alphabet of all possible letters
NSArray *arrayOfLCLetters = [[NSArray alloc] initWithObjects:#"a", #"b", #"c", #"d",#"e", #"f", #"g", #"h", #"i", #"j", #"k", #"l", #"m", #"n", #"o", #"p", #"qu", #"r", #"s", #"t", #"u", #"v", #"w", #"x",#"y", #"z",#"ch", #"sh", #"th", nil];
[self setArrayOfLowerCaseLetters: arrayOfLCLetters];
[arrayOfLCLetters release];arrayOfLCLetters = nil;
//create an array of phrases.
// These must correspond with each of the letters. e.g. a = apple.
NSArray *phrases= [[NSArray alloc ] initWithObjects:
#"apple",
#"butterfly",
#"cat",
#"dog",
#"egg",
#"frog",
#"ghost",
#"horse",
#"igloo",
#"jam",
#"kite",
#"leaf",
#"moon",
#"nut",
#"orange",
#"pig",
#"queen",
#"rabbit",
#"snake",
#"tree",
#"umbrella",
#"van",
#"water",
#"x-ray",
#"yak",
#"Zebra",
#"chair",
#"shoes",
#"thumb",
nil];
[self setArrayOfPhrases:phrases];
[phrases release]; phrases = nil;
//choose a random number to be the index reference for
// each goal letter and goal phrase.
[self set_indexForGoalLetter:(arc4random()%[_arrayOfLowerCaseLetters count])];
NSLog(#"index for goal letter is:, %i", _indexForGoalLetter);
//set Goal letter and goal phrase
[self setGoalLetter: [_arrayOfLowerCaseLetters objectAtIndex: _indexForGoalLetter]];
[self setGoalPhrase: [_arrayOfPhrases objectAtIndex:_indexForGoalLetter ]];
//set current level
[self set_currentLevel: level];
//[self set_currentLevel: 2];
//set number of whackholes by level
[self set_numberOfWhackHoles: [self numberOfWhackHolesByLevel:_currentLevel]];
//generate size of Letter pool by level
[self set_numberOfLettersInGameLetterPool:[self numberOfLettersInLetterPoolbyLevel:_currentLevel]];
////////////////////////////
/// Game letter pool
///////////////////////////
//set up array ton hold the pool of letters
NSMutableArray *gp = [[NSMutableArray alloc] initWithCapacity:_numberOfLettersInGameLetterPool];
[self setGameLetterPool: gp];
[gp release];gp = nil;
//add the goal letter to this pool
[_gameLetterPool addObject:_goalLetter];
int i = 1;
while (i < _numberOfLettersInGameLetterPool) {
NSString *letter = [_arrayOfLowerCaseLetters objectAtIndex:(arc4random()%[_arrayOfLowerCaseLetters count])];
if ([_gameLetterPool containsObject:letter] == false)
{
[_gameLetterPool addObject:letter];
i++;
}
}
NSLog(#"********** Game created ***************");
NSLog(#"pool of letters is: %#", [_gameLetterPool description]);
NSLog(#"****************************************");
NSLog(#"current goal letter is: %#", _goalLetter);
NSLog(#"****************************************");
NSLog(#"current goal phrase is: %#", _goalPhrase);
NSLog(#"****************************************");
return self;
}
-(void)dealloc
{
[super dealloc];
[_arrayOfLowerCaseLetters release]; _arrayOfLowerCaseLetters = nil;
[_arrayOfPhrases release]; _arrayOfPhrases = nil;
[_goalLetter release];_goalLetter = nil;
[_goalPhrase release]; _goalPhrase = nil;
[_gameLetterPool release];_gameLetterPool = nil;
}
The number one problem is that [super dealloc] must be the absolute last thing you do in -dealloc. This is because it is the dealloc method in NSObject that actually frees the memory, so by the time you get back to it, your instance variable pointers may already be garbage.
Other issues:
In init, do self = [super init]; The super object is allowed to return a different self pointer on init.
startNewGameOfLevel: and quitTheCurrentGameAndStartNewGame should use the property, not the bare instance variable.
-(void)startNewGameOfLevel:(NSInteger)level
{
if(![self game])
{
Game *g = [[Game alloc]initGamewithLevel:level];
[self setGame:g];
[g release]; g = nil;// g = nil, not necessary when it's about to go out of scope
}
[self set_currentlevel:[[self game] _currentLevel]]; // don't use _ to start methods - Apple reserves this convention
// set up popover to show the rounds goal letter
[self setUpPopOver];
}
-(void)quitTheCurrentGameAndStartNewGame
{
[self setGame: nil];
[self clearGamePlayingField];
animationStepIndex = 0;
[self startNewGameOfLevel: _currentlevel];
}
There are probably other issues in the body of your code - make sure you build with static analysis enables - it will catch many of them.