SKPhysicsBody bodyWithPolygonFromPath memory leaks - objective-c

I have a strange memory leaks when creating Sprite Kit physics bodies with custom shapes. This is how my implementation looks:
CGFloat offsetX = self.frame.size.width * self.anchorPoint.x;
CGFloat offsetY = self.frame.size.height * self.anchorPoint.y;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 4 - offsetX, 3 - offsetY);
CGPathAddLineToPoint(path, NULL, 66 - offsetX, 3 - offsetY);
CGPathAddLineToPoint(path, NULL, 35 - offsetX, 57 - offsetY);
CGPathCloseSubpath(path);
self.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:path];
CGPathRelease(path);
Everything is going on inside SKSpriteNode method. Instruments tells me about several memory leaks after creating such bodies:
Leaked object:
Malloc 32 Bytes
Size:
32 Bytes
Responsible Library:
PhysicsKit
Responsible Frame:
std::__1::__split_buffer<PKPoint, std::__1::allocator<PKPoint>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<PKPoint>&)
The CGPathRelease(path); line is necessary - without it I'm getting more memory leaks about CGPath which is understandable. When I'm using this implementation instead (for testing purposes):
CGFloat radius = MAX(self.frame.size.width, self.frame.size.height) * 0.5f;
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:radius];
...everything is working well, without memory leaks. I wonder if this is Sprite Kit bug or I'm doing something wrong.

This is a bug in sprite kit. You will have to wait for a fix.

Does anything change if you do the following?
CGPathRef pathCopy = CGPathCreateCopy(path);
CGPathRelease(path);
self.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:pathCopy];
CGPathRelease(pathCopy);

The only restriction on the path parameter is:
A convex polygonal path with counterclockwise winding and no self intersections. The points are specified relative to the owning node’s origin.
I do not know the values of offsetX and offsetY so I do not know whether the path is correct or not, but assuming that they are both 0, it seems to me like this path is clockwise and not counterclockwise. I would create the path using constants and no variables, just to make sure that it is correct and if it is still leaking I would say it's a bug in PhysicsKit.

As commented in several places, this looks like a bug in the implementation of SKPhysicsBody, which persists at least until iOS 7.1. The reason for this is:
SKPhysicsBody holds an instance variable, '_path', which holds a copy of the initial CGPathRef passed when calling 'bodyWithEdgeChainFromPath' or similiar constructors. This instance variable never gets released, so all paths will stay in memory.
However, you may implement a workaround for this by
(1) subclassing the SKShapeNode, that should hold the SKPhysicsBody,
(2) after creating and assigning the SKPhysicsBody for this node, retrieve the instance variable referring to the CGPathRef of the SKPhysicsBody,
(3) when the shape node is deallocated, check the retain count of the path. If it's > 0, release it and the memory leaks are gone.
There's little code overhead (beside subclassing all shape nodes, that use physics bodies relying on CGPath's). Just do this:
Add an instance variable to your subclass:
{
CGPathRef myPath;
}
Implement the method to retrieve the value for this CGPath in any subclassed SKShapeNode implementation. You might also consider to add this as a general category on SKNode:
- (CGPathRef) getPhysicsBodyPath
{
CGPathRef path = nil;
if ( self.physicsBody ){
object_getInstanceVariable(self.physicsBody, "_path", (void**) &path);
}
return(path);
}
This code will return the CGPathRef instance used by the node's physics body. Note, however, that this must be done immediately after assigning the physics body to the node. At a later time (i.e. in dealloc(), this might return a null value. So, after creating the body, store this value in the instance variable 'myPath'. To make this code work even after Apple might fix the bug, we'll add an additonal retain, which will make sure we can access this object once our SKNode is deallocated (see below):
/*
* we need to keep a copy of the final path reference, that has been created for the physics body.
* retrieving this value during deallocation won't work any more...
*/
myPath = CFRetain([self getPhysicsBodyPath]);
Finally, overwrite the 'dealloc' method and release the path, after your SKNode is released:
- (void) dealloc
{
self.physicsBody = nil;
[super dealloc];
if ( myPath != nil ) {
/* this will work, because we've retained the path for this instance */
CFIndex rc = CFGetRetainCount (myPath);
/* this is our own release ... */
CGPathRelease(myPath);
/* in case Apple has fixed the release, this is OK, otherwise we'll need
* to release the path twice to avoid memory leaks
*/
if ( rc > 1 ) {
CGPathRelease(myPath);
}
}
}
This will finally release this path and fix the memory leaks. This code works for all iOS version up to 7.1 and it also should work on further versions, once Apple finally fixes this bug and SKPhysicsBoy actually releases the path (as they are supposed to do).

I've also experienced similar memory leak, but it was fixed after I removed one line from my viewDidLoad function
skView.showsPhysics = YES;

Related

Heap growth related to use of CCLabelTTF

I am using Cocos2d 1.01. I am having undesired heap growth. To identify what is causing the growth I took a baseline snapshot, did a state change and return to zero state and repeated the heapshot. I found the primary cause of the heap growth to be non-object and then looked at the stack trace, noting that the problematic code appeared to be centered around CCLabelTTF.
Here is the code that seems to be problematic:
NSString *desc = [pEffectDescriptions objectAtIndex:i];
CCLabelTTF *descrptionLabel = [CCLabelTTF labelWithString:desc dimensions:CGSizeMake(290, 65) alignment:UITextAlignmentLeft fontName:#"Verdana-Italic" fontSize:10];
descrptionLabel.anchorPoint = ccp(0,0);
descrptionLabel.color = ccc3(192, 192, 192);
descrptionLabel.position = ccp(aSprite.position.x + 8, aSprite.position.y);
[self addChild:descrptionLabel z:10 tag:COMPARTMENT0+9600+i];
I don't understand what the problem is, because before returning to state zero, the following code is executed:
for (int i=0; i<1000; i++) {
if ([self getChildByTag:COMPARTMENT0+9000+i])
[self removeChildByTag:COMPARTMENT0+9000+i cleanup:true];
}
My reasoning is that the CCLabelTTF is owned by the layer and it in turn owns the NSString (the array also retains the NSString). However, when I remove the CCLabelTTF from the layer and its dealloc gets called it should therefore release the CCLabelTTF, which would then dealloc. Could the array reference to the NSString be responsible for preventing the deallocation of CCLabelTTF?
Anyone have a clue?
While adding labels you add 9600 to the tag, while removing you only add 9000 to the tag. Could that be it?
PS: I suggest using bitmap fonts, they use less memory, create, update and render faster.

How to switch two objects in objective-c

I am trying to find if collision occurs between two rectangles in objective-c. I thought one way to accomplish this would be detect the rectangle that is closest to 0,0 point then do rest of the work.
I wrote a function that takes two rectangle objects as parameters and does the math to calculate area, distance to origin etc....
So lets say rect1 is at (100,200) and rect1's width is 100 height 200, rect2 is at 150,150 and rect2's width is 100 height 200 this is calculated by function well enough.
If I switch rec1 and rect2 properties, so rect1 will be at 150,150 while rect2 will be at 100,200. And call following function
-(Rectangle*)intersect:(Rectangle*)rectA:(Rectangle*)rectB{
//check closest rectangle to 0,0 and switch rectangles
if (rectA.origin.x>rectB.origin.x) {
Rectangle *temporary = [[Rectangle alloc] init];
temporary=rectA;
rectA=rectB;
rectB=temporary;
[temporary release];
}
float rectAX = rectA.origin.x;
float rectAY = rectA.origin.y;
float rectBX = rectB.origin.x;
float rectBY = rectB.origin.y;
When I enable guard malloc and zombies I get following error:
-[Rectangle origin]: message sent to deallocated instance 0x100acffd0
As soon as rectA.origin.x; is called I get the error.
So Howcome rectA or rectB is deallocated? What is the correct way to switch two objects that has bunch of properties ?
There is a built in function for comparing CGRects CGRectIntersectsRect(rectA, rectB) that you can use to check your rectangle's frames :)
As far as your code for switching you have created a third object by allocing temporary. Then you set the temporary pointer at rectA and then you release rectA at the end since its pointing to temporary. Leaving the newly created object as a leak and then sending messages to the released rectA.
You don't really want to swap object pointers like that if you can help it in my experience. But if you absolutely have to and understand what's going on you could do it like this:
// Create copies of your objects
Rectangle *rectACopy = [rectA copy];
Rectangle *rectBCopy = [rectB copy];
// release the originals.
[rectA release];
[rectB release];
// Set your copies to the original pointers.
rectA = rectBCopy;
rectB = rectACopy;
NSCopying Protocol
First you need to implement the protocol.
#interface Rectangle : NSObject <NSCopying>
Then you need to create the new method. This will create a new object but with all the same values.
- (id)copyWithZone:(NSZone *)zone
{
id copy = [[[self class] alloc] init];
if (copy) {
// Copy NSObject based properties like UIViews, NSArrays, Subclassed objects.
[copy setObjectProperty:[self.objectProperty copy]];
// Set primitives like ints, CGRects, Bools.
[copy setPrimitiveProperty:self.primitiveProperty];
}
return copy;
}
You don't need to allocate a new object instance for temporary (and therefore you don't need to release it either). You are just taking your 2 existing pointers and switching them around. You're correct to use a 3rd variable (temporary) but you don't need to allocate any new space because you're not moving anything in memory, just swapping which variables point to the existing objects.
-(Rectangle*)intersect:(Rectangle*)rectA:(Rectangle*)rectB{
//check closest rectangle to 0,0 and switch rectangles
if (rectA.origin.x>rectB.origin.x) {
//Rectangle *temporary = [[Rectangle alloc] init]; // here you don't need to allocate as you are not using this object
// So use
Rectangle *temporary=rectA;
rectA=rectB;
rectB=temporary;
//[temporary release]; //you don't need this. Here you were releasing rectA not the temp rect that you allocated just below if, as you assign rectA to temporary
}
float rectAX = rectA.origin.x;
float rectAY = rectA.origin.y;
float rectBX = rectB.origin.x;
float rectBY = rectB.origin.y;

How do i set a CGContextRef property rather than using UIGraphicsGetCurrentContext()

Ive been trying to work with UIGraphicsGetCurrentContext and CGContextRef. I was told that using UIGraphicsGetCurrentContext() numerous times is bad and to rather work on using CGContextRef and referring to it.
I have been trying to work on the second part and I am having issues setting the #property and referring to it. Can someone give me a declaration and usage example of this? Tried googling and couldn't find any references to it.
Ta
You probably shouldn't store the return value from UIGraphicsGetCurrentContext in a property. You usually either don't know how long the context will be valid, or the context has a short lifetime anyway. For example, if you're calling UIGraphicsGetCurrentContext from your drawRect: method, you don't know how long that context will survive after you return from drawRect:. If you're calling UIGraphicsGetCurrentContext after calling UIGraphicsBeginImageContextWithOptions, you will probably be calling UIGraphicsEndImageContext soon anyway. It would be inappropriate to keep references to those contexts around.
If you are calling many Core Graphics functions on the same context, then you want to store the context in a local variable. For example, here is a drawRect: method from one of my test projects:
- (void)drawRect:(CGRect)dirtyRect {
NSLog(#"drawRect:%#", NSStringFromCGRect(dirtyRect));
[self layoutColumnsIfNeeded];
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc); {
// Flip the Y-axis of the context because that's what CoreText assumes.
CGContextTranslateCTM(gc, 0, self.bounds.size.height);
CGContextScaleCTM(gc, 1, -1);
for (NSUInteger i = 0, l = CFArrayGetCount(_columnFrames); i < l; ++i) {
CTFrameRef frame = CFArrayGetValueAtIndex(_columnFrames, i);
CGPathRef path = CTFrameGetPath(frame);
CGRect frameRect = CGPathGetBoundingBox(path);
if (!CGRectIntersectsRect(frameRect, dirtyRect))
continue;
CTFrameDraw(frame, gc);
}
} CGContextRestoreGState(gc);
}
You can see that I'm doing a bunch of stuff with the context: I'm saving and restoring the graphics state, I'm changing the CTM, and I'm drawing some Core Text frames into it. Instead of calling UIGraphicsGetCurrentContext many times, I call it just once and save the result in a local variable named gc.

cocos2d sub-classing

I know that cocos2d has scheduling callbacks to do nice things but when you need to use one CCAction (like CCMoveTo one) in order to move a sprite from position a to b, you do not have the ability to make small position arrangements to the sprite position for as long as the action is in effect.
The only possible way I found is by making a sub-class of CCMoveTo in order to check for obstacles and therefore provide some kind of movement to the left or right to a sprite that was moving from top to the bottom of the iPhone screen. The problem is that the sub-class does not have access to the parent class' instance variables (like the startPosition_ one) because they have not been declared as properties.
So I used the following snippet to overcome this situation but I wonder if I am doing something wrong...
- (void)myUpdate:(ccTime)time {
if(delegate && method_) {
NSNumber *num = (NSNumber *)[delegate performSelector:method_ withObject:ownTarget];
if(num) {
double xpos = [num doubleValue];
[num release];
CCMoveTo *parent = [super retain];
parent->startPosition_.x += xpos;
[parent release];
}
[super update:time];
}
Is it correct to retain/release the super-class? The "[super update:time];" at the bottom of the code will make the final positioning.
CCMoveTo *parent = [super retain];
Ouch! This statement makes absolutely no sense. It is the same as writing:
[self retain];
As for accessing the super class' instance variables: unless they're declared #private you can access them. I just checked: they're not #private. You should be able to write in your subclass:
startPosition_.x += xpos;
If that doesn't work make sure your class is really a subclass of CCMoveTo, and not some other class.
Finally, I'd like to say that actions are very limited when it comes to implementing gameplay. You're probably much better off to simply animate your game objects by modifying their position property every frame, based on a velocity vector. You have much more freedom over the position and position updates, and none of the side effects of actions such as a one-frame delay every time you run a new action.
-(void) update:(ccTime)delta
{
// modify velocity based on whatever you need, ie gravity, or just heading in one direction
// then update the node's position by adding the current velocity to move it:
self.position = CGPointMake(self.position.x + velocity.x, self.position.y + velocity.y);
}

Is there somethone wrong with my Object Scope?

This is a program I'm writing (myself as opposed to copying someone else's and thus not learning) as part of the ObjectiveC and Cocoa learning curve. I want to draw simple shapes on a NSView (limiting it to ovals and rectangles for now). The idea is that I record each NSBezierPath to an NSMutableArray so I can also investigate/implement saving/loading, undo/redo. I have a canvas, can draw on it as well as 2 buttons that I use to select the tool. To handle the path I created another object that can hold a NSBezierPath, color values and size value for each object drawn. This is what I want to store in the array. I use mouseDown/Dragged/Up to get coordinates for the drawing path. However, this is where things go wonky. I can instantiate the object that is supposed to hold the path/color/etc. info but, when I try to change an instance variable, the app crashes with no useful message in the debugger. I'll try to keep my code snippets short but tell me if I need to include more. The code has also degenerated a little from me trying so many things to make it work.
Project: Cocoa document based app
I have the following .m/.h files
MyDocument:NSDocument - generated by XCode
DrawnObject:NSObject - deals with the drawn object i.e. path, color, type (oval/rect) and size
Canvas:NSView - well, shows the drawing, deals with the mouse and buttons
Canvas is also responsible for maintaining a NSMutableArray of DrawnObject objects.
DrawnObject.h looks like this:
#import <Foundation/Foundation.h>
//The drawn object must know what tool it was created with etc as this needs to be used for generating the drawing
#interface DrawnObject : NSObject {
NSBezierPath * aPath;
NSNumber * toolType;//0 for oval, 1 for rectangular etc....
float toolSize;
struct myCol{
float rd;
float grn;
float blu;
float alp;
} toolColor;
}
-(void)setAPath:(NSBezierPath *) path;
-(NSBezierPath *)aPath;
#property (readwrite,assign) NSNumber * toolType;
-(float)toolSize;
-(void)setToolSize:(float) size;
-(struct myCol *)toolColor;
-(void)setCurrentColor:(float)ref:(float)green:(float)blue:(float)alpha;
#end
Canvas.h looks like this
#import
#import "drawnObject.h"
#interface Canvas : NSView {
NSMutableArray * myDrawing;
NSPoint downPoint;
NSPoint currentPoint;
NSBezierPath * viewPath;//to show the path as the user drags the mouse
NSNumber * currentToolType;
BOOL mouseUpFlag;//trying a diff way to make it work
BOOL mouseDrag;
}
-(IBAction)useOval:(id)sender;
-(IBAction)useRect:(id)sender;
-(IBAction)showTool:(id)sender;
-(NSRect)currentRect;
-(NSBezierPath *)createPath:(NSRect) aRect;
-(void)setCurrentToolType:(NSNumber *) t;
-(NSNumber *)currentToolType;
#end
In the Canvas.m file there are several functions to deal with the mouse and NSView/XCode also dropped in -(id)initWithFrame:(NSRect)frame and -(void)drawRect:(NSRect)rect Originally I use mouseUp to try to insert the new DrawnObject into the array but that caused a crash. So, now I use two BOOL flags to see when the mouse was released (clunky but I'm trying....)in drawRect to insert into the array. I've included the method below and indicated where it causes the app to fail:
- (void)drawRect:(NSRect)rect { //This is called automatically
// Drawing code here.
//NSLog(#"Within drawRect tool type is %d", [self currentTool]);
NSRect bounds = [self bounds];
NSRect aRect = [self currentRect];
viewPath = [self createPath:aRect];
//the createPath method uses the tool type to switch between oval and rect bezier curves
if(mouseUpFlag==YES && mouseDrag==YES){
mouseDrag=NO;
//Create a new drawnObject here
DrawnObject * anObject = [[DrawnObject alloc]init];//- WORKS FINE UP TO HERE
NSLog(#"CREATED NEW drawnObject");
[anObject setAPath:viewPath]; //- INSTANT APP DEATH!!!!
NSLog(#"Set a path in drawnObject");
[anObject setToolType:[[NSNumber alloc]initWithInt:5]];
NSLog(#"Set toolType in DrawnObject");
[anObject setToolType:currentToolType];
[myDrawing addObject:anObject];
NSLog(#"Added Object");
}
[[NSColor colorWithCalibratedRed:0.0 green:0.9 blue:0.0 alpha:0.5]set];
[NSBezierPath fillRect:bounds];
[[NSColor lightGrayColor]set];
[viewPath stroke]; //This is so the user can see where the drawing is being done
//Now, draw the paths in the array
[[NSColor blueColor]set];
for(DrawnObject * indexedObject in myDrawing){
[[indexedObject aPath] stroke];//This will do the actual drawing of ALL objects
}
}
I guess this has something to do with object scope or something but I just can not figure it out. As I said, as I've tried things the code has sort of undergone an metamorphosis, sadly not for the better. Like those BOOLS etc.
HELP! Any clever people out there, point me in the right direction please!
ADDED THIS ON:
-(NSBezierPath *)createPath:(NSRect) aRect
{
NSBezierPath * tempPath;
//I need to know what tool
switch(0){ //temporary - this would use the toolType as a selector
case 0:
tempPath = [NSBezierPath bezierPathWithOvalInRect:aRect];
break;
case 1:
tempPath = [NSBezierPath bezierPathWithRect:aRect];
break;
default:
tempPath = [NSBezierPath bezierPathWithOvalInRect:aRect];
break;
}
return tempPath;
}
You said your init method was:
-(void)init {
[super init];
//set default color = black
toolColor.rd=1.0;
toolColor.grn=1.0;
toolColor.blu=1.0;
toolColor.alp=1.0;
//set default size
toolSize=0.8;
//set default toolType
toolType=0;
//oval
NSLog(#"Init %#",self);
}
This is definitely wrong; read up on how to create an init method in the Obj-C guide or by reading sample code. Here's what it should look like:
-(id)init {
if (self = [super init]) {
//set default color = black
toolColor.rd=1.0;
toolColor.grn=1.0;
toolColor.blu=1.0;
toolColor.alp=1.0;
//set default size
toolSize=0.8;
//set default toolType
toolType=0;
//oval
NSLog(#"Init %#",self);
}
return self;
}
By not returning anything from -init, you were preventing the object's creation. Good luck! :-)
Edit: Ashley beat me to it...
What do you mean by “crash”?
Does anything appear in the Debugger Console (⇧⌘R)?
Does a stack trace appear in the Debugger window?
If there's a stack trace, where in your code does it crash?
It just hangs. In the debugger I see:
[Session started at 2008-11-28 14:40:34 +1000.]
2008-11-28 14:40:36.157 CH18Challenge_try2[1893:10b] Mouse Down at (80.000000,285.000000)
2008-11-28 14:40:36.333 CH18Challenge_try2[1893:10b] Mouse Up at (166.000000,217.000000)
2008-11-28 14:40:36.348 CH18Challenge_try2[1893:10b] Init
2008-11-28 14:40:36.349 CH18Challenge_try2[1893:10b] CREATED NEW drawnObject
[Session started at 2008-11-28 14:40:36 +1000.]
Loading program into debugger…
GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin".Program loaded.
sharedlibrary apply-load-rules all
Attaching to program: `/Users/johan_kritzinger/Documents/Cocoa/CH18Challenge_try2/build/Debug/CH18Challenge_try2.app/Contents/MacOS/CH18Challenge_try2', process 1893.
(gdb)
Then I have to force quit to stop it.
We need to see the implementation of setAPath from DrawnObject.m. Also, for the "stack trace" look on the upper left of the debugger--it should list a stack of functions showing where in your code the crash is. Make sure you're running in Debug mode, not Release.
On the command line you can type print-object and you can
set a breakpoint in that line and step through it from there. It seems setAPath is somehow broken
Regards
Friedrich
What you have is not a crash. A crash is when a signal is raised (like EXC_BAD_ACCESS) or an uncaught exception.
What you have seems to be an infinite loop.
You need to use the pause button in the Debugger and see exactly where. I would guess that you have an infinite loop in your setAPath: method. You need to work out why this function is looping indefinitely.