ARC is kicking my ARC
I installed cocos 2d and was unhappy seeing as I wanted to use ARC. I am having an issue that I can't quite seem to figure out.
Instruments is saying I have 5 references to my GraphicsManager object, yet I can only conclude 4 of them. I need to create and release many of these.
Class "RowManager" (CCLayer) calls class "GraphicsManager" (CCNode) to make these Objects. RowManager calls its methods to move the objects via selector:
[self schedule:#selector(row:) interval:.01]
Finally, when the object arrives at a point, method "row1Finished" is called who then creates one more reference to the object before calling the object to (die) remove it's own children.
I can get all three animations to stop, get the object to detach from the RowManager and disappear, yet the dealloc method still does not call itself. What am I doing wrong?
To convert my non-arc cocos2d template I followed the instructions here:
http://www.learn-cocos2d.com/2012/04/enabling-arc-cocos2d-project-howto-stepbystep-tutorialguide/
/////////////////
#interface RowManager : CCLayer {
GraphicsManager *graphicsObject;
}
#implementation RowManager
-(id) init
{
if( (self=[super init]) ) {
graphicsObject = [[GraphicsManager alloc] initWithBoatType:#"flatboat" withWord:#"life" usingCharacter:#"Puchai" orientation:#"up" visible:YES];
graphicsObject.tag = 21;
graphicsObject.position = ccp(offset,size.height/4.1);
[self addChild: graphicsObject];
[self schedule:#selector(row1:) interval:.01];
[self schedule:#selector(rotate:) interval:6];
// etc…
-(void)row1:(ccTime)dt{
// scroll the object across the screen
graphicsObject = (GraphicsManager *)[self getChildByTag:21]; // reference: for the row method
graphicsObject.position = ccp(graphicsObject.position.x-1, graphicsObject.position.y);
if (graphicsObject.position.x < 0 - graphicsObject.boatWidth){
[self row1Finished];
}
}
-(void)rotate:(ccTime)dt {
//waggle the object a bit
graphicsObject = (GraphicsManager *)[self getChildByTag:21]; // reference 2: for the rotate method
[graphicsObject runAction:[CCSequence actions:
[CCRotateBy actionWithDuration:1 angle:.8],
[CCRotateBy actionWithDuration:2 angle:-.8],
[CCRotateBy actionWithDuration:1 angle:-.8],
[CCRotateBy actionWithDuration:2 angle:.8],
nil]];
}
-(void) row1Finished {
graphicsObject = (GraphicsManager *)[self getChildByTag:21]; // reference 3: to reference the referenced
[self unschedule:#selector (row1:)];
[self unschedule:#selector(rotateBoat:)];
[graphicsObject die]; // call within object class to remove references
[self removeChild:graphicsObject cleanup:YES];
graphicsObject = nil;
}
/////////////////////
in Graphics Manager Class:
///////////////////
#interface GraphicsManager : CCNode {...}
#property CGFloat boatWidth;
…etc
#implementation GraphicsManager
#synthesize boatWidth;
- (void) die {
[self unschedule:#selector(puchaiRow:)]; // reference 4 animation
[self stopAllActions];
[self removeAllChildrenWithCleanup:YES]; // removes sub objects created in class
NSLog(#"Died");
}
- (void)dealloc
{
CCLOG(#"Graphics Manager dealloc: %#", self);
}
-(id)initWithBoatType:(NSString *)boatType orientation:(NSString *)orientation
{
self = [super init];
if (self){
if ([orientation isEqual: #"upstream"]){
flatBoat = [CCSprite spriteWithFile:#"flatBoat.png"];
flatBoat.anchorPoint = ccp(0,0);
self.boatWidth = [flatBoat boundingBox].size.width;
[self addChild:flatBoat];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"puchai.plist"];
puchai = [CCSprite spriteWithSpriteFrameName:#"puchai1.png"];
puchai.anchorPoint = ccp(0,0);
puchai.position = ccp(20,30);
puchai.tag = 4;
[self addChild:puchai];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"cube.plist"];
cube = [CCSprite spriteWithSpriteFrameName:#"cube1.png"];
cube.scale = .80;
cube.tag = 56;
cube.visible = NO;
cube.position = ccp(150,45);
[self addChild:cube];
cube = [CCSprite spriteWithSpriteFrameName:#"cube2.png"];
cube.scale = .80;
cube.position = ccp(-1 +150,[cube boundingBox].size.height +45);
cube.tag = 57;
cube.visible = NO;
[self addChild:cube];
[self schedule:#selector(puchaiRow:) interval:3]; // reference 4 animation
flatBoatWing = [CCSprite spriteWithFile:#"flatBoatWing.png"];
flatBoatWing.anchorPoint = ccp(0,0);
flatBoatWing.position = ccp(25,17);
[self addChild:flatBoatWing];
}
///etc
-(void)puchaiRow:(ccTime)dt{
puchai = (CCSprite *)[self getChildByTag:4];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"puchai.plist"];
NSMutableArray *frames = [[NSMutableArray alloc]init];
for (int i = 1; i<=8; i++) {
NSString *frameName = [NSString stringWithFormat:#"puchai%i.png",i ];
[frames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:frameName]];
}
CCAnimation *a = [CCAnimation animationWithSpriteFrames:frames delay:1.0f/5.0f];
[puchai runAction:[CCAnimate actionWithAnimation:a]];
//////////
here is the zombies:history output.
# Address Category Event Type RefCt Timestamp Size Responsible Caller
0 0x13ea95e0 GraphicsManager Malloc 1 00:02.748.439 208 -[RowManager init]
1 0x13ea95e0 GraphicsManager Retain 2 00:02.748.447 0 -[GraphicsManager initWithBoatType:withWord:usingCharacter:orientation:visible:]
2 0x13ea95e0 GraphicsManager Release 1 00:02.748.449 0 -[GraphicsManager initWithBoatType:withWord:usingCharacter:orientation:visible:]
3 0x13ea95e0 GraphicsManager Retain 2 00:02.841.543 0 -[CCScheduler scheduleSelector:forTarget:interval:paused:repeat:delay:]
4 0x13ea95e0 GraphicsManager Retain 3 00:02.841.572 0 ccArrayAppendObjectWithResize
5 0x13ea95e0 GraphicsManager Retain 4 00:03.477.669 0 -[RowManager row1:] <--- AHA!!!!
6 0x13ea95e0 GraphicsManager Retain 5 00:03.482.438 0 -[RowManager row1Finished]
7 0x13ea95e0 GraphicsManager Release 4 00:03.482.439 0 -[RowManager row1Finished]
8 0x13ea95e0 GraphicsManager Release 3 00:03.482.480 0 -[CCScheduler removeHashElement:]
9 0x13ea95e0 GraphicsManager Release 2 00:03.483.435 0 ccArrayRemoveObjectAtIndex
10 0x13ea95e0 GraphicsManager Release 1 00:03.483.436 0 -[RowManager row1Finished]
you can use (__bridge id) to exclude from ARC, what do you need to exclude & use manual Retain-Release.
Should [self unschedule:#selector (row1:)]; be [self unschedule:#selector (row:)];? That would keep an extra reference alive.
Solved the problem by reviewing the zombies history one more time. In row1, I did not set the object to nil after calling row1finished, breaking the law: for each reference in the same class you must "nillify" all references to zero...
Moral of the story: the reason it was difficult to find the bug was because I had scheduled the row1 routine and was letting it run about 50 times, causing a bunch of retain releases in the zombies history making it impossible to reduce by elimination. I commented out the surrounding code getting rid of all that and was able to get a zombies output that i could parse.
Thanks all for your input! I guess I know how to use instruments a little better now!
-(void)row1:(ccTime)dt{
graphicsObjectRow1 = (GraphicsManager *)[self getChildByTag:11];
graphicsObjectRow1.position = ccp(graphicsObjectRow1.position.x + 1,graphicsObjectRow1.position.y);
// if (graphicsObjectRow1.position.x > screenX/4){
[self row1Finished];
graphicsObjectRow1 = nil;
// graphicsObjectRow1.position = ccp(offset,size.height/4.1);
// }
}
Related
I have a problem with my cocos2d game. I am trying to delete a projectile shot by an enemy every 5 seconds (each projectile is supposed to have a lifetime of 5 seconds), but I cannot figure out how to do it. I get the error of
Assertion failure in -[CCTimer initWithTarget:selector:interval:]
Here is my code:
-(void)projectileShooting:(ccTime)dt
{
[self schedule:#selector(projectileShooting:) interval:2.5];
projcount++;
if([proj count] <= 15 ){
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
for(CCSprite *projectile in proj){
[self schedule:#selector (deleteprojectile:projectile:) interval:5];
}
}
}
}
for(CCSprite *enem2 in enemytwo)
{
if( [proj count] <= 15){
CCSprite * projectilebull = [CCSprite spriteWithFile:#"Projectile.png"];
CGPoint MyVector = ccpSub(enem2.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enem2.contentSize.width/2+10);
MyVector = ccpMult(MyVector,-1);
projectilebull.position = ccpAdd(enem2.position, MyVector);
[self addChild:projectilebull];
[proj addObject:projectilebull];
for(CCSprite *projectile in proj){
}
}
}
}
-(void)deleteprojectile:(CCSprite *)protime:(ccTime)dt{
NSMutableArray *timepro = [[NSMutableArray alloc]init];
[timepro addObject:protime];
for(CCSprite *objecttime in timepro){
[proj removeObject:objecttime];
[self removeChild:objecttime cleanup:YES];
}
}
It's a bit of a hack but this is what I use in my program, until I find a more elegant solution. I have a method in my game layer that I call to remove a node from its parent, like so:
-(void)removeNode:(CCNode*)node {
[node removeFromParentAndCleanup:YES];
}
And when I want to schedule a node for deletion after a delay, I call it like this:
[self performSelector:#selector(removeNode:) withObject:node afterDelay:delay];
Simple, and it works.
Change argument name in your selector to protime instead of projectile. The selector must match the signature defined in you object's class definition.
Your selector was not defined properly and probably the Timer is checking if the object implements the given selector.
I did not have time to test it so thanks to #RamyAlZuhouri for confirming.
First of all, I'm an Objective-C novice. So I'm not very familiar with OS X or iOS development. My experience is mostly in Java.
I'm creating an agent-based modeling-framework. I'd like to display the simulations and to do that I'm writing a little application. First, a little bit about the framework. The framework has a World class, in which there is a start method, which iterates over all agents and has them perform their tasks. At the end of one "step" of the world (i.e., after all the agents have done their thing), the start method calls the intercept method of an object that implements InterceptorProtocol. This object was previously passed in via the constructor. Using the interceptor, anyone can get a hook into the state of the world. This is useful for logging, or in the scenario that I'm trying to accomplish: displaying the information in a graphical manner. The call to intercept is synchronous.
Now as far as the GUI app is concerned, it is pretty simple. I have a controller that initializes a custom view. This custom view also implements InterceptorProtocol so that it can listen in, to what happens in the world. I create a World object and pass in the view as an interceptor. The view maintains a reference to the world through a private property and so once I have initialized the world, I set the view's world property to the world I have just created (I realize that this creates a cycle, but I need a reference to the world in the drawRect method of the view and the only way I can have it is if I maintain a reference to it from the class).
Since the world's start method is synchronous, I don't start the world up immediately. In the drawRect method I check to see if the world is running. If it is not, I start it up in a background thread. If it is, I examine the world and display all the graphics that I need to.
In the intercept method (which gets called from start running on the background thread), I set setNeedsToDisplay to YES. Since the start method of the world is running in a separate thread, I also have a lock object that I use to synchronize so that I'm not working on the World object while it's being mutated (this part is kind of janky and it's probably not working the way I expect it to - there are more than a few rough spots and I'm simply trying to get a little bit working; I plan to clean up later).
My problem is that the view renders some stuff, and then it pretty much locks up. I can see that the NSLog statements are being called and so the code is running, but nothing is getting updated on the view.
Here's some of the pertinent code:
MasterViewController
#import "MasterViewController.h"
#import "World.h"
#import "InfectableBug.h"
#interface MasterViewController ()
#end
#implementation MasterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_worldView = [[WorldView alloc] init];
World* world = [[World alloc] initWithName: #"Bhumi"
rows: 100
columns: 100
iterations: 2000
snapshotInterval: 1
interceptor: _worldView];
for(int i = 0; i < 999; i++) {
NSMutableString* name = [NSMutableString stringWithString: #"HealthyBug"];
[name appendString: [[NSNumber numberWithInt: i] stringValue]];
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: name
layer: #"FirstLayer"
infected: NO
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
}
NSLog(#"Added all bugs. Going to add infected");
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: #"InfectedBug"
layer: #"FirstLayer"
infected: YES
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
[_worldView setWorld: world];
//[world start];
}
return self;
}
- (NSView*) view {
return self.worldView;
}
#end
WorldView
#import "WorldView.h"
#import "World.h"
#import "InfectableBug.h"
#implementation WorldView
#synthesize world;
- (id) initWithFrame:(NSRect) frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void) drawRect:(NSRect) dirtyRect {
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
CGContextClearRect(myContext, CGRectMake(0, 0, 1024, 768));
NSUInteger rows = [world rows];
NSUInteger columns = [world columns];
NSUInteger cellWidth = 1024 / columns;
NSUInteger cellHeight = 768 / rows;
if([world running]) {
#synchronized (_lock) {
//Ideally we would need layers, but for now let's just get this to display
NSArray* bugs = [world bugs];
NSEnumerator* enumerator = [bugs objectEnumerator];
InfectableBug* bug;
while ((bug = [enumerator nextObject])) {
if([bug infected] == YES) {
CGContextSetRGBFillColor(myContext, 128, 0, 0, 1);
} else {
CGContextSetRGBFillColor(myContext, 0, 0, 128, 1);
}
NSLog(#"Drawing bug %# at %lu, %lu with width %lu and height %lu", [bug name], [bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight);
CGContextFillRect(myContext, CGRectMake([bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight));
}
}
} else {
[world performSelectorInBackground: #selector(start) withObject: nil];
}
}
- (BOOL) isFlipped {
return YES;
}
- (void) intercept: (World *) aWorld {
struct timespec time;
time.tv_sec = 0;
time.tv_nsec = 500000000L;
//nanosleep(&time, NULL);
#synchronized (_lock) {
[self setNeedsDisplay: YES];
}
}
#end
start method in World.m:
- (void) start {
running = YES;
while(currentIteration < iterations) {
#autoreleasepool {
[bugs shuffle];
NSEnumerator* bugEnumerator = [bugs objectEnumerator];
Bug* bug;
while((bug = [bugEnumerator nextObject])) {
NSString* originalLayer = [bug layer];
NSUInteger originalX = [bug x];
NSUInteger originalY = [bug y];
//NSLog(#"Bug %# is going to act and location %i:%i is %#", [bug name], [bug x], [bug y], [self isOccupied: [bug layer] x: [bug x] y: [bug y]] ? #"occupied" : #"not occupied");
[bug act];
//NSLog(#"Bug has acted");
if(![originalLayer isEqualToString: [bug layer]] || originalX != [bug x] || originalY != [bug y]) {
//NSLog(#"Bug has moved");
[self moveBugFrom: originalLayer atX: originalX atY: originalY toLayer: [bug layer] atX: [bug x] atY: [bug y]];
//NSLog(#"Updated bug position");
}
}
if(currentIteration % snapshotInterval == 0) {
[interceptor intercept: self];
}
currentIteration++;
}
}
//NSLog(#"Done.");
}
Please let me know if you'd like to see any other code. I realize that the code is not pretty; I was just trying to get stuff to work and I plan on cleaning it up later. Also, if I'm violating an Objective-C best practices, please let me know!
Stepping out for a bit; sorry if I don't respond immediately!
Whew, quiet a question for probably a simple answer: ;)
UI updates have to be performed on the main thread
If I read your code correctly, you call the start method on a background thread. The start method contains stuff like moveBugFrom:... and also the intercept: method. The intercept method thus calls setNeedsDisplay: on a background thread.
Have all UI related stuff perform on the main thread. Your best bet is to use Grand Central Dispatch, unless you need to support iOS < 4 or OS X < 10.6 (or was it 10.7?), like this:
dispatch_async(dispatch_get_main_queue(), ^{
// perform UI updates
});
I've just started with the sparrow framework, and have been following "The Big Sparrow Tutorial" by Gamua themselves. I'm on the first part of the tutorial, using the AppScaffold 1.3 but when I try to compile my basic code it hangs at the loading screen and gives me a SIGABRT error.
I put an exception breakpoint, and it stopped here, in GameController.m (seen at bottom) of the AppScaffold:
mGame = [[Game alloc] initWithWidth:gameWidth height:gameHeight];
This was also my only output:
2012-07-30 07:19:54.787 AppScaffold[1682:10a03] -[Game initWithWidth:height:]: unrecognized selector sent to instance 0x7553980
(lldb)
I am using the stock AppScaffold, the only thing I changed was the Game.m.
This is my Game.m:
#interface Game : SPSprite
#end
#implementation Game
{
#private
SPImage *mBackground;
SPImage *mBasket;
NSMutableArray *mEggs;
}
- (id)init
{
if((self = [super init]))
{
//load the background image first, add it to the display tree
//and keep it for later use
mBackground = [[SPImage alloc] initWithContentsOfFile:#"background.png"];
[self addChild:mBackground];
//load the image of the basket, add it to the display tree
//and keep it for later use
mBasket = [[SPImage alloc] initWithContentsOfFile:#"basket.png"];
[self addChild:mBasket];
//create a list that will hold the eggs,
//which we will add and remove repeatedly during the game
mEggs = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc
{
[mBackground release];
[mBasket release];
[mEggs release];
[super dealloc];
}
#end
I've tried my best to use my basic troubleshooting tactics, but I'm very new to Obj-C and Sparrow and could use a hand :)
Thanks
EDIT: I've addded the GameController.m contents here for clarity:
//
// GameController.m
// AppScaffold
//
#import <OpenGLES/ES1/gl.h>
#import "GameController.h"
#interface GameController ()
- (UIInterfaceOrientation)initialInterfaceOrientation;
#end
#implementation GameController
- (id)initWithWidth:(float)width height:(float)height
{
if ((self = [super initWithWidth:width height:height]))
{
float gameWidth = width;
float gameHeight = height;
// if we start up in landscape mode, width and height are swapped.
UIInterfaceOrientation orientation = [self initialInterfaceOrientation];
if (UIInterfaceOrientationIsLandscape(orientation)) SP_SWAP(gameWidth, gameHeight, float);
mGame = [[Game alloc] initWithWidth:gameWidth height:gameHeight];
mGame.pivotX = gameWidth / 2;
mGame.pivotY = gameHeight / 2;
mGame.x = width / 2;
mGame.y = height / 2;
[self rotateToInterfaceOrientation:orientation animationTime:0];
[self addChild:mGame];
}
return self;
}
- (void)dealloc
{
[mGame release];
[super dealloc];
}
- (UIInterfaceOrientation)initialInterfaceOrientation
{
// In an iPhone app, the 'statusBarOrientation' has the correct value on Startup;
// unfortunately, that's not the case for an iPad app (for whatever reason). Thus, we read the
// value from the app's plist file.
NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary];
NSString *initialOrientation = [bundleInfo objectForKey:#"UIInterfaceOrientation"];
if (initialOrientation)
{
if ([initialOrientation isEqualToString:#"UIInterfaceOrientationPortrait"])
return UIInterfaceOrientationPortrait;
else if ([initialOrientation isEqualToString:#"UIInterfaceOrientationPortraitUpsideDown"])
return UIInterfaceOrientationPortraitUpsideDown;
else if ([initialOrientation isEqualToString:#"UIInterfaceOrientationLandscapeLeft"])
return UIInterfaceOrientationLandscapeLeft;
else
return UIInterfaceOrientationLandscapeRight;
}
else
{
return [[UIApplication sharedApplication] statusBarOrientation];
}
}
- (void)rotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
animationTime:(double)animationTime
{
float angles[] = {0.0f, 0.0f, -PI, PI_HALF, -PI_HALF};
float oldAngle = mGame.rotation;
float newAngle = angles[(int)interfaceOrientation];
// make sure that rotation is always carried out via the minimal angle
while (oldAngle - newAngle > PI) newAngle += TWO_PI;
while (oldAngle - newAngle < -PI) newAngle -= TWO_PI;
// rotate game
if (animationTime)
{
SPTween *tween = [SPTween tweenWithTarget:mGame time:animationTime
transition:SP_TRANSITION_EASE_IN_OUT];
[tween animateProperty:#"rotation" targetValue:newAngle];
[[SPStage mainStage].juggler removeObjectsWithTarget:mGame];
[[SPStage mainStage].juggler addObject:tween];
}
else
{
mGame.rotation = newAngle;
}
// inform all display objects about the new game size
BOOL isPortrait = UIInterfaceOrientationIsPortrait(interfaceOrientation);
float newWidth = isPortrait ? MIN(mGame.gameWidth, mGame.gameHeight) :
MAX(mGame.gameWidth, mGame.gameHeight);
float newHeight = isPortrait ? MAX(mGame.gameWidth, mGame.gameHeight) :
MIN(mGame.gameWidth, mGame.gameHeight);
if (newWidth != mGame.gameWidth)
{
mGame.gameWidth = newWidth;
mGame.gameHeight = newHeight;
SPEvent *resizeEvent = [[SPResizeEvent alloc] initWithType:SP_EVENT_TYPE_RESIZE
width:newWidth height:newHeight animationTime:animationTime];
[mGame broadcastEvent:resizeEvent];
[resizeEvent release];
}
}
// Enable this method for the simplest possible universal app support: it will display a black
// border around the iPhone (640x960) game when it is started on the iPad (768x1024); no need to
// modify any coordinates.
/*
- (void)render:(SPRenderSupport *)support
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
glEnable(GL_SCISSOR_TEST);
glScissor(64, 32, 640, 960);
[super render:support];
glDisable(GL_SCISSOR_TEST);
}
else
[super render:support];
}
*/
#end
Here is my Xcode project: http://cl.ly/2e3g02260N47
You are calling a
initWithWidth:height:
method, while none is defined in your class.
From your edit, it seems that the initWithWidth method is declared in the class GameController, not in Game.
So, it seems that the
In which context are you calling initWithWidth:height: method is declared in Game.h but you define it in GameController.m.
This explains both why you get the SIGABRT and the errors when compiling.
The fix is calling
mGame = [[GameController alloc] init];
from GameController initWithWidth...
- (id)initWithWidth:(float)width height:(float)height
{
if ((self = [super initWithWidth:width height:height]))
{
float gameWidth = width;
float gameHeight = height;
// if we start up in landscape mode, width and height are swapped.
UIInterfaceOrientation orientation = [self initialInterfaceOrientation];
if (UIInterfaceOrientationIsLandscape(orientation)) SP_SWAP(gameWidth, gameHeight, float);
mGame = [[Game alloc] init];
mGame.pivotX = gameWidth / 2;
mGame.pivotY = gameHeight / 2;
mGame.x = width / 2;
mGame.y = height / 2;
[self rotateToInterfaceOrientation:orientation animationTime:0];
[self addChild:mGame];
}
return self;
}
The tutorial was very old and was incompatible with the latest scaffold;
I did this:
- (id)init
{
if((self = [super init]))
{
when I should've done this:
- (id)initWithWidth:(float)width height:(float)height
{
if ((self = [super initWithWidth:width height:height]))
{
thanks, though sergio!
(There are much better sparrow tutorials and I'm even making my own video tutorials :P)
[Edit] I rearranged how I remove obj from the list. That's originally how it was still causing the same error.
This is the code in question. I believe I have the error narrowed down to the line where it removes the object from the _blocks NSMutableArray. I have this exact same code in other parts of my code removing similar object from the same array. For some reason when this function is called it causes the game to crash. It didn't have this problem before I upgraded to the latest XCode which supports iOS SDK 5 and armv7. Before I upgraded XCode it worked fine. I would walk into an item and the item would disappear from the screen. Now it just crashes when I get an item. Any help would be greatly appreciated.
-(void)itemCollision:(Collidable *)obj :(int)itemID :(int)objID: (bool)withPlayer{
[background removeChild:[obj getSprite] cleanup:YES];
[background removeChild:[obj getArrow] cleanup:YES];
[_blocks removeObject:obj];
//[obj release];
if(withPlayer){
if(itemID == 1){
[[SimpleAudioEngine sharedEngine] playEffect:#"Item2.mp3" pitch:1.0f pan:0.0f gain:0.3f];
points += (10 + level * 22);
}
}
}
#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, #"AppDelegate");
[pool release];
return retVal;
}
When the app crashes it highlights this line:
int retVal = UIApplicationMain(argc, argv, nil, #"AppDelegate");
The error message is: "Thread 1: Program received signal: "SIGARBRT".
Here is the code where I call itemCollision:
-(bool) isCollision {
CCSprite *playerSprite = mob;//[mob getSprite];
CGRect playerRect = CGRectMake(playerSprite.position.x - (playerSprite.contentSize.width/2),
playerSprite.position.y - (playerSprite.contentSize.height/2),
playerSprite.contentSize.width,
playerSprite.contentSize.height);
//Player Collision
BOOL collision = FALSE;
for (Block *block in _blocks) {
CCSprite *blockSprite = [block getSprite];
CGRect blockRect = CGRectMake(blockSprite.position.x - (blockSprite.contentSize.width/2),
blockSprite.position.y - (blockSprite.contentSize.height/2),
blockSprite.contentSize.width,
blockSprite.contentSize.height + 1 + [mob getSpeed]);
if (CGRectIntersectsRect(blockRect, playerRect)) {
if ([block getItemID] != 0) {
[self itemCollision:block :[block getItemID] :[block getID] :TRUE];
continue;
}
if(playerSprite.position.y > blockSprite.position.y){
collision = TRUE;
}
if(collision) {
[[SimpleAudioEngine sharedEngine] playEffect:#"squish.caf"];
[self die];
return TRUE;
}
}
}
return FALSE;
}
Here is how I remove objects from _blocks elsewhere in the code. This is in the gravity timer function:
NSMutableArray *blocksToDelete = [[NSMutableArray alloc] init];
for (Block *block in _blocks) {
CCSprite *blockSprite = [block getSprite];
//[block addSpeed:gravity];
[block setPreX:blockSprite.position.x];
[block setPreY:blockSprite.position.y];
blockSprite.position = ccp(blockSprite.position.x, blockSprite.position.y - [block getSpeed]);
if (blockSprite.position.y < -30 + blockSprite.contentSize.height / 2) {
[blocksToDelete addObject:block];
}
if(blockSprite.position.y - blockSprite.contentSize.height < [block getArrow].position.y){
[block getArrow].visible = FALSE;
}
}
for (Block *b in blocksToDelete) {
[_blocks removeObject:b];
[background removeChild:[b getSprite] cleanup:YES];
[background removeChild:[b getArrow] cleanup:YES];
[b release];
}
I get to the items before they reach the point where they get deleted by this function. Again, this code has been working for months until I upgraded my XCode.
If the array had the last retain on the object, it will be dealloced when you remove it. You go on to use the object for the rest of the method, so it needs to stay alive. Certainly checking the retainCount of obj to see if the NSMutableArray has the last retain is the first place to start.
If we assume, for the moment, that that's the case, then try this alternate approach:
-(void)itemCollision:(Collidable *)obj :(int)itemID :(int)objID: (bool)withPlayer
{
[[obj retain] autorelease];
[_blocks removeObject:obj];
[background removeChild:[obj getSprite] cleanup:YES];
[background removeChild:[obj getArrow] cleanup:YES];
if(withPlayer){
if(itemID == 1){
[[SimpleAudioEngine sharedEngine] playEffect:#"Item2.mp3" pitch:1.0f pan:0.0f gain:0.3f];
points += (10 + level * 22);
}
}
}
When the debugger goes to main.m, it's often because it's dying while popping an autorelease pool -- a task you don't have debug symbols for, so it goes to the deepest stack frame for which it has source to show you, which in the case of autorelease pools on the main thread, will be your main() function.
You should also try running this case with the Zombies instrument in Instruments. It can be very helpful for tracking this stuff down.
It's hard to know exactly what's happening beyond the scope of the code sample you've shared, but a wild guess would be that your 'obj' is being over-released. It's released once when you remove it from the array, then you're releasing it again. This may or may not be appropriate considering the retain count of that object as its passed in.
To begin troubleshooting, try using the Instrument Zombie tool - that'll help you observe if it is indeed being overreleased.
You could additionally comment out your:
[obj release]
To see if that stops the error (which may or may not be the solution but should be insightful in any case).
I'm working on an iPad app that has a slider that is used to scroll through data. When scrolling, a map is displayed and the data is updated. The problem is, if you scroll fast enough (or somehow trigger the race condition), the app crashes on accessing a zombie NSString. I've been able to track it down in the Profiler and found this:
Event Type RefCt Timestamp Size Responsible Library Responsible Caller
Malloc 1 01:55.166.466 16 Foundation -[NSPlaceholderString initWithFormat:locale:arguments:]
Autorelease <null> 01:55.166.472 0 Foundation +[NSString stringWithFormat:]
CFRetain 2 01:55.166.473 0 My Program -[StateView updateVotes:]
CFRetain 3 01:55.166.476 0 UIKit -[UILabel setText:]
CFRelease 2 01:55.166.504 0 My Program -[StateView updateVotes:]
CFRelease 1 01:55.177.661 0 Foundation -[NSAutoreleasePool release]
CFRelease 0 01:55.439.090 0 UIKit -[UILabel setText:]
Zombie -1 01:55.439.109 0 UIKit -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:lineBreakMode:letterSpacing:includeEmoji:]
I'm using ARC on iOS5, so I'm not in control of the retain/release at all. Even if I was, looking at the above, it is correct. The problem seems to be a race condition between the drawing function and the UILabel string actually changing. The UILabel releases the first string, as a new one has been set, but the drawing function is holding a reference to it somehow, but did not retain it.
As a note, I have not modified the UILabel in any way.
Any ideas?
--- Code added as update:
Slider update:
-(void)sliderValueChanged:(UISlider *)slider {
float position = slider.value - 1790.0f;
int year;
if(position <= 0.0f) {
year = 1789;
} else {
year = 1792 + (floor(position / 4.0f)*4);
}
[self setYear:year];
}
setYear:
-(void)setYear:(int)year {
if (year == currentYear) {
// year didn't change, so don't do anything
return;
}
[yearLabel setText:[[NSString alloc] initWithFormat:#"%i", year]];
currentYear = year;
[self getMapForYear:year];
}
getMapForYear:
-(void) getMapForYear:(int)year {
[self setToMap:[historicalData objectForKey:[NSNumber numberWithInt:year]];
}
setToMap:
-(void) setToMap:(HistoricalMap *)map {
// Label the map
for (State *state in [map states]) {
[mapController setVotes:[state votes] forState:[state abbreviation]];
}
}
setVotes:forState:
-(void)setVotes:(NSNumber *)votes forState:(NSString *)stateAbbreviation {
StateView *state = [states objectForKey:stateAbbreviation];
if (state == nil) {
NSLog(#"Invalid State Votes -- %#", stateAbbreviation);
return;
}
[state updateVotes:votes];
[state setNeedsDisplay];
}
updateVotes:
-(void)updateVotes:(NSNumber *)newVotes {
[self setVotes:newVotes];
NSString *voteString = [[NSString alloc] initWithFormat:#"%#", newVotes];
[voteLabel setText:voteString];
if ([newVotes isEqual:[NSNumber numberWithInt:0]]) {
[[self voteLabel] setHidden:YES];
[[self stateAbbreviationLabel] setHidden:YES];
} else {
[[self stateAbbreviationLabel] setHidden:NO];
[[self voteLabel] setHidden:NO];
}
}
I think you are attempting to do far too much during the slider's movement. Creating and executing core data fetch requests alone would seem to be overkill, let alone updating the entire GUI and a screenful of labels. Have you tested the performance of this on a device?
It could be worth profiling these sections of code and seeing where the time is spent. You could look at caching the fetch requests or the results, for example, or you may have to resort to updating only when the slider has stopped, or only for every n increments along the path.
You havw several memory-leaks with NSString:
[yearLabel setText:[[NSString alloc] initWithFormat:#"%i", year]]; // leak
Create string with stringWithFormat method instead
[yearLabel setText:[NSString stringWithFormat:#"%i", year]];
[NSString stringWithFormat: **is the best way formatting the string than any other..**