简体   繁体   中英

Passing an Objective-C Object into a CGFunctionRef

I am trying to write a CGFunctionRef that will act as a shading function for a CGShadingRef object in my project that uses ARC. I am trying to pass a NSMutableArray (filled with UIColors) into my CGFunctionRef callback.

Here is my init method

- (void)initInternal {  
    _colors = [[NSMutableArray alloc] init];

    // Creating the colors in this way ensures that the underlying color space is UIDeviceRGBColorSpace
    // and thus has 4 color components: red, green, blue, alpha
    [_colors addObject:[UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f]]; // Red
    [_colors addObject:[UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f]]; // Green
    [_colors addObject:[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f]]; // Blue
    [_colors addObject:[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f]]; // White
    [_colors addObject:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]]; // Black

    // Define the shading callbacks
    CGFunctionCallbacks callbacks;
    callbacks.version = 0;                      // Defaults to 0
    callbacks.evaluate = CGShadingCallback;     // This is our color selection function
    callbacks.releaseInfo = NULL;               // Not used

    // As input to our function we want 1 value in the range [0.0, 1.0].
    // This is our position within the 'gradient'.
    size_t domainDimension = 1;
    CGFloat domain[2] = {0.0f, 1.0f};

    // The output of our function is 4 values, each in the range [0.0, 1.0].
    // This is our selected color for the input position.
    // The 4 values are the red, green, blue and alpha components.
    size_t rangeDimension = 4;
    CGFloat range[8] = {0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f};

    // Create the shading function
    _shadingFunction = CGFunctionCreate(&_colors, domainDimension, domain, rangeDimension, range, &callbacks);   
}

Here is my callback method

static void CGShadingCallback(void* info, const float* inData, float* outData) {
    // Our colors
    NSMutableArray* colors = (__bridge_transfer NSMutableArray*)info;
    // Position within the gradient, ranging from 0.0 to 1.0
    CGFloat position = *inData;

    // Find the color that we want to used based on the current position;
    NSUInteger colorIndex = position * [colors count];

    // Account for the edge case where position == 1.0
    if (colorIndex >= [colors count])
        colorIndex = [colors count] - 1;

    // Get our desired color from the array
    UIColor* color = [colors objectAtIndex:colorIndex];

    // Copy the 4 color components (red, green, blue, alpha) to outData
    memcpy(outData, CGColorGetComponents(color.CGColor), 4 * sizeof(CGFloat));  
}

Here is my drawRect method

- (void)drawRect:(CGRect)rect {
    CGRect b = self.bounds;
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create a simple elliptic path
    CGContextAddEllipseInRect(ctx, b);
    // Set the current path as the clipping path
    CGContextClip(ctx);

    // Create our shading using the function that was defined earlier.
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGShadingRef shading = CGShadingCreateAxial(colorspace,
                                            CGPointMake(CGRectGetMinX(b), CGRectGetMidY(b)),
                                            CGPointMake(CGRectGetMaxX(b), CGRectGetMidY(b)),
                                            _shadingFunction,
                                            true,
                                            true);

    // Draw the shading
    CGContextDrawShading(ctx, shading);


    // Cleanup
    CGShadingRelease(shading);
    CGColorSpaceRelease(colorspace);
}

If I just use __bridge in my callback method, then is crashes on
NSMutableArray* colors = (__bridge NSMutableArray*)info; with an EXC_BAD_ACCESS.

If I use __bridge_transfer, it crashes on CGContextDrawShading(ctx, shading); in the drawRect with an EXC_BAD_ACCESS.

You create a array and use that array as the context of a CGFunction .

So that array needs to be retained when given to the function. You can do this with the __bridge_retained keyword:

CGFunctionCallbacks callbacks;
callbacks.version = 0;
callbacks.evaluate = CGShadingCallback;
callbacks.releaseInfo = myReleaseCallback;

_shadingFunction = CGFunctionCreate((__bridge_retained void *)_colors, domainDimension, domain, rangeDimension, range, &callbacks);

Then, you must not use in __bridge_transfer in the drawing callback. __bridge_transfer cast the value into a strong reference without retaining it (ownership is transferred). This is equivalent to releasing the array. As your callback may be invoked many times, this is not the right place to release the array.

The array must be released when the function is destroyed. This is the purpose of the releaseInfo callback:

static void myReleaseCallback(void *info) {
    CFRelease(info);
}

You can also do this with a __bridge_transfer cast but that's is not very elegant:

static void myReleaseCallback(void *info) {
    NSArray *array = (__bridge_transfer NSArray *)info;
    // now we need to do something with the array if we don't want a compiler warning
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM