Search code examples
iosobjective-ccore-graphicscgpathclang-static-analyzer

Xcode analyzer is showing leak for CGPathRef when it's in a class instance method?


I have a class GraphicView that basically creates a UIView that uses a CAShapeLayer as its default layer object:

GraphicView.h

#import <UIKit/UIKit.h>

@interface GraphicView : UIView
{
    CAShapeLayer *_shapeLayer;
}
@property(nonatomic, strong) CAShapeLayer *shapeLayer;

- (void) loadPath:(CGPathRef)path;

@end

GraphicView.m

#import "GraphicView.h"

@implementation GraphicView

- (id) initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.shapeLayer = (CAShapeLayer *) self.layer;
    }
    return self;
}

+ (Class)layerClass
{
    return [CAShapeLayer class];
}

- (void) loadPath:(CGPathRef)path
{
    CGMutablePathRef tempPath = path;
    self.shapeLayer.path = tempPath;
    CGPathRelease(tempPath);
}

@end

When I call loadPath:

GraphicViewController.m

- (void) loadSquarePath
{
    [self.graphicView loadPath:[SquarePath newSquarePath]];
}

SquareBalloonPath.m

- (CGMutablePathRef) newSquareBalloonPath
{
    CGSize mutablePathInitialSize = [self squareBalloonPathInitialSize];
    CGMutablePathRef mutablePath = CGPathCreateMutable();
    CGPathMoveToPoint(mutablePath, NULL, 0, 0);
    CGPathAddRect(mutablePath, NULL, CGRectMake(0, 0, mutablePathInitialSize.width, mutablePathInitialSize.height));
    CGPathCloseSubpath(mutablePath);
    return mutablePath;
}

I get an analyzer error saying "Potential leak of an object of type 'CGMutablePathRef'". However, when I move loadPath from GraphicView to GraphicViewController (and change the method header to loadGraphicView:WithPath:

GraphicViewController.m

- (void) loadGraphicView:(GraphicView *)graphicView withPath:(CGPathRef)path
{
    CGPathRef tempPath = path;
    graphicView.shapeLayer.path = tempPath;
    CGPathRelease(tempPath);
}

- (void) loadSquarePath
{
    [self.graphicView loadPath:[SquarePath newSquarePath]];
}

The analyzer error goes away! Is there something I have to tell the compiler so I can get loadPath to work when it's included as an instance method within GraphicView?


Solution

  • The problem is with ownership. You have one place that creates and takes ownership of the CGMutablePathRef. You then pass that reference to another method that then releases it. But that method should not take responsibility for releasing the reference. It has no idea if the reference it is getting should be released or not.

    The simple fix is to put the responsibility in the correct place. Change loadSquarePath in GraphicViewController to something like this:

    - (void) loadSquarePath
    {
        CGMutablePathRef path = [SquarePath newSquareBalloonPath];
        [self.graphicView loadPath:path];
        CGPathRelease(path);
    }
    

    and remove the call to CGPathRelease from loadPath: in GraphicView.

    Or in the second set of code, remove CGPathRelease from loadGraphicView:withPath: in GraphicViewController.

    These changes quiet the analyzer because it moves the responsibility of releasing to the same place that took the responsibility of ownership at creation.

    This also eliminates possible crashes due to the CGReleasePath in loadPath: over-releasing paths that should not be released. While your current code is only passing a path reference that does need to be released, that may not always be the case.