I need some advice on __bridge
-ing in iOS.
Hopefully the example below will explain the problem better than I can in words, but I need to know how I can convert a void*
to an NSMutableArray*
(i.e., which __bridge
variation should be used).
Reading about the different bridges, I deduced that I would need __bridge_transfer
but then I receive an EXC_BAD_ACCESS on addObject:
Ultimately, I'd like to have an array of the CGPoints
in the CGPath
after CGPathApply
has been called.
#import <Foundation/Foundation.h>
void _processPathElement(void* info, const CGPathElement* element)
{
NSMutableArray *array = (/* WHAT BRIDGE HERE */ NSMutableArray*) info;
switch (element->type)
{
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
{
CGPoint point = element->points[0];
[array addObject:[NSValue valueWithCGPoint:point]];
break;
}
default:
break;
}
}
int main(int argc, char *argv[])
{
@autoreleasepool
{
//Create path
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint( path, NULL, 0, 0);
CGPathAddLineToPoint(path, NULL, 1, 0);
CGPathAddLineToPoint(path, NULL, 1, 1);
CGPathAddLineToPoint(path, NULL, 0, 1);
CGPathCloseSubpath(path);
NSMutableArray *pathPoints = [NSMutableArray array];
CGPathApply(path, &pathPoints, _processPathElement);
NSLog(@"Points:%@", pathPoints);
}
}
The documentation on the use of the bridge keyword can be found here. Specifically, I want to point out §3.2.4:
(__bridge T) op
casts the operand to the destination type T. If T is a retainable object pointer type, then op must have a non-retainable pointer type. If T is a non-retainable pointer type, then op must have a retainable object pointer type. Otherwise the cast is ill-formed. There is no transfer of ownership, and ARC inserts no retain operations.
(__bridge_retained T) op
casts the operand, which must have retainable object pointer type, to the destination type, which must be a non-retainable pointer type. ARC retains the value, subject to the usual optimizations on local values, and the recipient is responsible for balancing that +1.
(__bridge_transfer T) op
casts the operand, which must have non-retainable pointer type, to the destination type, which must be a retainable object pointer type. ARC will release the value at the end of the enclosing full-expression, subject to the usual optimizations on local values.
The pointer you're being passed in (void*
) is a non retainable pointer type, whereas your NSMutableArray is a retainable pointer type. This rules out __bridge_retained
straight away. So the question is, to __bridge
or to __bridge_transfer
?
__bridge_transfer
is typically used when you want the Objective-C pointer from a method that returns a CF Object that has been retained. For example, CFStringCreateWithFormat will return a retained CFString, but if you want an NSString from it, you need to __bridge_transfer
between them. This will make ARC release the object that CF retained when appropriate. For example, NSString* str = (__bridge_transfer NSString*) CFStringCreateWithFormat(...);
Your code isn't doing that, you don't need to meddle with the ownership. Your main method is in control of its memory management, and is simply passing a reference to a method it calls (albeit indirectly, but it's all within the scope of main). As such, you would use __bridge
.
But wait, when I use __bridge, my code gets memory access errors!?
Ah, this is an issue with the code you posted, and isn't in relation to the whole bridging discussion. You need to pass a void*
to CGApplyPath, for your processing function _processPathElement
. What you're passing is NSMutableArray**
.
When you recast to the NSMutableArray*
, you're actually casting a NSMutableArray**
. This will cause the infamous EXC_BAD_ACCESS. You need to pass the pointer itself, not a pointer to a pointer. But, CGPathApply(path, pathPoints, _processPathElement)
will not work, you cannot pass off a NSMutableArray*
as a void*
. What you need (ironically), is a bridge. For the same reasons as before, all you need is __bridge
. See below the code, with the correct bridges in place, and working as expected:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
void _processPathElement(void* info, const CGPathElement* element)
{
NSMutableArray *array = (__bridge NSMutableArray*) info;
switch (element->type)
{
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
{
CGPoint point = element->points[0];
[array addObject:[NSValue valueWithCGPoint:point]];
break;
}
default:
break;
}
}
int main(int argc, char *argv[])
{
@autoreleasepool
{
//Create path
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint( path, NULL, 0, 0);
CGPathAddLineToPoint(path, NULL, 1, 0);
CGPathAddLineToPoint(path, NULL, 1, 1);
CGPathAddLineToPoint(path, NULL, 0, 1);
CGPathCloseSubpath(path);
NSMutableArray *pathPoints = [[NSMutableArray alloc] init];
CGPathApply(path, (__bridge void*)pathPoints, _processPathElement);
NSLog(@"Points:%@", pathPoints);
}
}
This will print out:
Points:(
"NSPoint: {0, 0}",
"NSPoint: {1, 0}",
"NSPoint: {1, 1}",
"NSPoint: {0, 1}"
)