I have a question similar to this one:
CGLayerRef in NSValue - when to call retain() or release()?
I am drawing 24 circles as radial gradients in a view. To speed it up I am drawing the gradient into a layer and then drawing the layer 24 times. This worked really well to speed up the rendering. On subsequent drawRect calls some of the circles may need to be redrawn with a different hue, while others remain the same.
Every time through drawRect I recalculate a new gradient with the new hue and draw it into a new layer. I then loop through the circles, drawing them with the original layer/gradient or new layer/gradient as appropriate. I have a 24 element NSMutableArray that stores a CGLayerRef for each circle.
I think this is the answer provided in the question I linked above, however it is not working for me. The second time through drawRect, any circle that is drawn using the CGLayerRef that was stored in the array causes the program to crash when calling CGContextDrawLayerAtPoint. In the debugger I have verified that the actual hex value of the original CGLayerRef is stored properly into the array, and in the second time through drawRect that the same hex value is passed to CGContextDrawLayerAtPoint.
Further, I find that if I don't CGLayerRelease the layer then the program doesn't crash, it works fine. This tells me that something is going wrong with the memory management of the layer. It's my understanding that storing an object into an NSArray will increment it's reference count, and it won't be deallocated until the array releases it.
Anyway, here is the relevant code from drawRect. Down at the bottom you can see that I commented out CGLayerRelease. In this configuration the app doesn't crash although I think this is a resource leak. If I uncomment that release then the app crashes the second time though drawRect (between the first and second calls one of the circles has it's led_info.selected property cleared, indicating that it should use the saved layer rather than the new layer:
NSLog(@"ledView drawing hue=%4f sat=%4f num=%d size=%d",hue_slider_value,sat_slider_value,self.num_leds,self.led_size);
rgb_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:1.0];
end_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:0.0];
NSArray *colors = [NSArray arrayWithObjects:
(id)rgb_color.CGColor, (id)end_color.CGColor, nil];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,(__bridge CFArrayRef) colors, NULL);
CGLayerRef layer = CGLayerCreateWithContext(context, (CGSize){self.led_size,self.led_size}, /*auxiliaryInfo*/ NULL);
if (layer) {
CGContextRef layer_context = CGLayerGetContext(layer);
CGContextDrawRadialGradient(layer_context, gradient, led_ctr,self.led_size/8,led_ctr, self.led_size/2,kCGGradientDrawsBeforeStartLocation);
} else {
NSLog(@"didn't get a layer");
}
for (int led=0;led<[self.led_info_array count];led++) {
led_info=[self.led_info_array objectAtIndex:led];
// the first time through selected=1 and led_info.cg_layer=nil for all circles,
// so this branch is taken.
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
led_info.cg_layer=layer;
// the second time through drawRect one or more circles have been deselected.
// They take this path through the if/else
} else {
CGPoint startPoint=led_info.rect.origin;
// app crashes on this call to CGContextDrawLayerAtPoint
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
}
// with this commented out the app doesn't crash.
//CGLayerRelease(layer);
Here is the declaration of led_info:
@interface ledInfo : NSObject
@property CGFloat hue;
@property CGFloat saturation;
@property CGFloat brightness;
@property int selected;
@property CGRect rect;
@property CGPoint center;
@property unsigned index;
@property CGLayerRef cg_layer;
- (NSString *)description;
@end
led_info_array is the NSMutableArray of ledInfo objects, the array itself is a property of the view:
@interface ledView : UIView
@property float hue_slider_value;
@property float sat_slider_value;
@property unsigned num_leds;
@property unsigned led_size;
@property unsigned init_has_been_done;
@property NSMutableArray *led_info_array;
//@property layerPool *layer_pool;
@end
The array is initialized like this: self.led_info_array = [[NSMutableArray alloc] init];
Edit: since I posted I have found that if I put retain/release around the assignemt into the NSMutableArray then I can also leave in the original CGLayerRelease and the app works. So I guess this is how it is supposed to work, although I'd like to know why the retain/release is necessary. In the objective C book I am reading (and the answer to the question linked above) I thought assigning into NSArray implicitly did retain/release. The new working code looks like this:
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
if (led_info.cg_layer) CGLayerRelease(led_info.cg_layer);
led_info.cg_layer=layer;
CGLayerRetain(layer);
} else {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
You can probably tell that I'm brand new to Objective C and iOS programming, and I realize that I'm not really sticking to convention regarding case and probably other things. I'll clean that up but right now I want to solve this memory management problem.
Rob, thanks for the help. I could use a little further clarification. I think from what you are saying that there are two problems:
1) Reference counting doesn't work with CGLayerRef. OK, but it would be nice to know that while writing code rather than after debugging. What is my indication that when using "things" in Objective C/cocoa that resource counting doesn't work?
2) You say that I'm storing to a property, not an NSArray. True, but the destination of the store is the NSArray via the property, which is a pointer. The value does make it into the array and back out. Does resource counting not work like this? ie instead of CGLayerRef, if I were storing some NSObject into NSArray using the code above would resource counting work? If not, then would getting rid of the intermediate led_info property and accessing the array directly from within the loop work?