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;
}
Community
  • 1
  • 1
spring
  • 18,009
  • 15
  • 80
  • 160
  • possible duplicate of [EXC\_ARM\_DA\_ALIGN error when running on a device](http://stackoverflow.com/questions/14129785/exc-arm-da-align-error-when-running-on-a-device) – rob mayoff Jan 22 '13 at 19:26
  • 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? – Rob Jan 22 '13 at 20:54
  • 1
    @Rob - I cleaned up some code, ran the `nap debugger` and now it works. – spring Jan 22 '13 at 22:24

2 Answers2

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;
diederikh
  • 25,221
  • 5
  • 36
  • 49
  • 1
    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? – spring Jan 22 '13 at 19:42
  • 1
    Huh. I didn't know those annotations were usable yet. That's awesome, thanks! (Docs are here, for the curious: http://clang.llvm.org/docs/AutomaticReferenceCounting.html#conversion-to-retainable-object-pointer-type-of-expressions-with-known-semantics) – Jesse Rusak Jan 22 '13 at 19:47
  • @skinnyTOD: The behavior accessing a released object is undefined. – diederikh Jan 22 '13 at 20:09
  • 2
    ARC does not manage CF objects. How would adding a qualifier to the method have any effect on memory management? – Duncan C Jan 25 '13 at 21:17
  • @DuncanC - it doesn't. adding these qualifiers had no effect on crashes i am experiencing. – Alex Gray Mar 28 '13 at 04:34
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);
}
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 2
    The CF_RETURNS_RETAINED annotation is actually only needed if your method doesn't conform to the [CoreFoundation naming rules](http://developer.apple.com/library/ios/DOCUMENTATION/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-103029). It's recommended to rename your methods instead. – Jan Gorman Aug 14 '13 at 07:59