Crash using [UIBezierPath CGPath] with CAShapeLayer under ARC
Asked Answered
A

2

5

I'm getting a BAD ACCESS error using [UIBezierPath CGPath] with CAShapeLayer under ARC. I've tried bridging in various ways but I'm not clear if that is the problem. I have isolated the crash to using the result of the makeToPath method:

 maskLayer = [CAShapeLayer layer];
 maskLayer.path = [self makeToPath];

But this doesn't crash:

 maskLayer = [CAShapeLayer layer];
 maskLayer.path = [self makeFromPath];

Is there something invalid with the path created by makeToPath? I'm planning to use the from and to paths with a CABasicAnimation once I sort this crash out. What is the correct ARC bridging for CGPathRefs from UIBezierPath?

-(CGPathRef)makeToPath
{
    UIBezierPath* triangle = [UIBezierPath bezierPath];
    [triangle moveToPoint:CGPointZero];
    [triangle addLineToPoint:CGPointMake(self.view.frame.size.width,0)];
    [triangle addLineToPoint:CGPointMake(0, self.view.frame.size.height)];
    [triangle closePath];
    return [triangle CGPath];
}

-(CGPathRef)makeFromPath
{
    UIBezierPath*rect = [UIBezierPath bezierPathWithRect:self.view.frame];
    return [rect CGPath];
}

UPDATE So I changed my .h file per an answer below but I am still getting the crash

-(CGPathRef)makeToPath CF_RETURNS_RETAINED;
-(CGPathRef)makeFromPath CF_RETURNS_RETAINED;

I also tried making my methods return a UIBezierPath instance per the answer here (shown below). Still no success. Anyone want to give me the longform explanation on how to fix this?

maskLayer.path = [[self makeToPath] CGPath];// CRASHES
morph.toValue =  CFBridgingRelease([[self makeToPath] CGPath]);// CRASHES

-(UIBezierPath*)makeToPath
{
    UIBezierPath* triangle = [UIBezierPath bezierPath];
    [triangle moveToPoint:CGPointZero];
    [triangle addLineToPoint:CGPointMake(self.view.frame.size.width,0)];
    [triangle addLineToPoint:CGPointMake(0, self.view.frame.size.height)];
    [triangle closePath];
    return triangle;
}
Agonizing answered 22/1, 2013 at 19:19 Comment(3)
possible duplicate of EXC_ARM_DA_ALIGN error when running on a deviceKadi
I use that final syntax, having makeToPath return UIBezierPath * and then using maskLayer.path = [[self makeToPath] CGPath];, all the time. But I'm also immediately using maskLayer, e.g. [self.view.layer addSublayer:maskLayer]. What are you doing with maskLayer after setting its path? Can you show us?Grovel
@Grovel - I cleaned up some code, ran the nap debugger and now it works.Agonizing
G
8

The problem is with returning the CGPath. The value returned is a CGPathRef which is not covered by ARC. The UIBezierPath you create is released after the method ends. Thus also freeing the CGPathRef. You can specify a source annotation to let ARC know your intent:

In the .h file:

-(CGPathRef)makeToPath CF_RETURNS_RETAINED;
-(CGPathRef)makeFromPath CF_RETURNS_RETAINED;
Geneva answered 22/1, 2013 at 19:35 Comment(5)
thanks - I'm not getting the crash with the method using the bezierPathWithRect function. That's confusing. Do you know why that approach doesn't crash?Agonizing
Huh. I didn't know those annotations were usable yet. That's awesome, thanks! (Docs are here, for the curious: clang.llvm.org/docs/…)Forget
@skinnyTOD: The behavior accessing a released object is undefined.Geneva
ARC does not manage CF objects. How would adding a qualifier to the method have any effect on memory management?Heartrending
@DuncanC - it doesn't. adding these qualifiers had no effect on crashes i am experiencing.Disqualify
H
3

As the other poster pointed out, you are returning a CGPath reference taken from a UIBezierPath object that goes out of scope at the end of the method. As the docs on the UIBezierPath CGPath property say:

The path object itself is owned by the UIBezierPath object and is valid only until you make further modifications to the path.

You need to create a copy of your CGPath and return that:

-(CGPathRef)makeToPath
{
    UIBezierPath* triangle = [UIBezierPath bezierPath];
    [triangle moveToPoint:CGPointZero];
    [triangle addLineToPoint:CGPointMake(self.view.frame.size.width,0)];
    [triangle addLineToPoint:CGPointMake(0, self.view.frame.size.height)];
    [triangle closePath];
    CGPathRef theCGPath = [triangle CGPath];
    return CGPathCreateCopy(theCGPath);
}

The way I read the link to the llvm project, I think that the cf_returns_retained qualifier is intended to tell the caller the memory management policy for the returned value, rather than doing the retain for you.

Thus I think you would both need to create a copy of the path AND add the cf_returns_retained qualifier. I'm not clear on the syntax of that qualifier, however. (Never used it before.)

Assuming the other poster had the right syntax, it would look something like this:

-(CGPathRef)makeToPath CF_RETURNS_RETAINED;
{
    UIBezierPath* triangle = [UIBezierPath bezierPath];
    [triangle moveToPoint:CGPointZero];
    [triangle addLineToPoint:CGPointMake(self.view.frame.size.width,0)];
    [triangle addLineToPoint:CGPointMake(0, self.view.frame.size.height)];
    [triangle closePath];
    CGPathRef theCGPath = [triangle CGPath];
    return CGPathCreateCopy(theCGPath);
}
Heartrending answered 25/1, 2013 at 21:17 Comment(1)
The CF_RETURNS_RETAINED annotation is actually only needed if your method doesn't conform to the CoreFoundation naming rules. It's recommended to rename your methods instead.Hazzard

© 2022 - 2024 — McMap. All rights reserved.