何时保留和发布CGLayerRef?

时间:2012-11-27 06:36:26

标签: xcode cocoa memory-management cglayer

我有一个与此类似的问题:

CGLayerRef in NSValue - when to call retain() or release()?

我在视图中绘制了24个圆圈作为径向渐变。为了加快速度,我将渐变绘制到一个图层中,然后将图层绘制24次。这非常适合加速渲染。在随后的drawRect调用中,可能需要使用不同的色调重绘某些圆圈,而其他圆圈保持不变。

每次通过drawRect我都会使用新色调重新计算一个新渐变并将其绘制到一个新图层中。然后我循环遍历圆圈,使用原始图层/渐变或新图层/渐变绘制它们。我有一个24元素的NSMutableArray,它为每个圆圈存储一个CGLayerRef。

我认为这是我在上面提到的问题中提供的答案,但它对我不起作用。第二次通过drawRect,使用存储在数组中的CGLayerRef绘制的任何圆圈都会导致程序在调用CGContextDrawLayerAtPoint时崩溃。在调试器中,我已经验证了原始CGLayerRef的实际十六进制值被正确存储到数组中,并且第二次通过drawRect将相同的十六进制值传递给CGContextDrawLayerAtPoint。

此外,我发现如果我没有CGLayerRelease该层,那么程序不会崩溃,它工作正常。这告诉我图层的内存管理出了问题。我的理解是将对象存储到NSArray中会增加它的引用计数,并且在数组释放它之前不会释放它。

无论如何,这是drawRect的相关代码。在底部你可以看到我注释掉了CGLayerRelease。在此配置中,应用程序不会崩溃,尽管我认为这是资源泄漏。如果我取消注释该版本,那么应用程序第二次崩溃但是drawRect(在第一次和第二次调用之间,其中一个圆圈清除了led_info.selected属性,表明它应该使用保存的层而不是新层:

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);

这是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是ledInfo对象的NSMutableArray,数组本身是视图的属性:

@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

数组初始化如下:     self.led_info_array = [[NSMutableArray alloc] init];

编辑:自从我发布以来,我发现如果我将assignemt中的retain / release放入NSMutableArray,那么我也可以保留原来的CGLayerRelease并且应用程序正常工作。所以我想这应该是如何工作的,尽管我想知道为什么保留/释放是必要的。在我正在阅读的目标C书中(以及上面链接的问题的答案)我认为分配到NSArray中隐含地保留/释放。新的工作代码如下所示:

    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);
    }

你可能会说我是Objective C和iOS编程的新手,我意识到我并不是真的坚持关于案例和其他事情的约定。我会清理它,但现在我想解决这个内存管理问题。

罗布,谢谢你的帮助。我可以进一步澄清一下。我想从你所说的有两个问题:

1)引用计数不适用于CGLayerRef。好的,但是在编写代码而不是在调试之后知道它会很好。当我在Objective C / cocoa中使用“东西”时,我的指示是资源计数不起作用吗?

2)你说我存储的是属性,而不是NSArray。是的,但商店的目的地是通过属性NSArray,这是一个指针。该值确实使其进入数组并退出。资源计算不能像这样工作吗?即,而不是CGLayerRef,如果我使用上面的代码将一些NSObject存储到NSArray中资源计数工作?如果没有,那么将摆脱中间的led_info属性并直接从循环工作中访问数组?

1 个答案:

答案 0 :(得分:0)

您不是直接将图层存储在NSArray中。您将其存储在ledInfo对象的属性中。

问题是CGLayer实际上不是Objective-C对象,因此ARC和编译器生成的(“合成”)属性设置器都不会为您保留和释放它。假设你这样做:

CGLayerRef layer = CGLayerCreateWithContext(...);
led_info.cg_layer = layer;
CGLayerRelease(layer);

编译器生成的cg_layer setter方法只是将指针存储在实例变量中,而不是其他任何内容,因为CGLayerRef不是Objective-C对象引用。因此,当您释放图层时,其引用计数将变为零并且已取消分配。现在你的cg_layer属性中有一个悬空指针,当你以后使用它时会崩溃。

修复方法是手动编写setter,如下所示:

- (void)setCg_layer:(CGLayerRef)layer {
    CGLayerRetain(layer);
    CGLayerRelease(_cg_layer);
    _cg_layer = layer;
}

请注意,在释放旧值之前保留新值非常重要。如果在保留新旧版本之前释放旧版本,并且新版本恰好与旧版本相同,则可以在中间解除分层图层!

更新

回复您的修改:

  1. 引用计数适用于CGLayerRef自动引用计数(ARC)没有。 ARC只适用于它认为是Objective-C对象的ARC, 一个Objective-C对象。 Objective-C对象(一般来说)是用CGLayerRef或块声明的类的实例。

    CGLayer Reference表示CGLayerRef派生自@interface,这是所有Core Foundation对象的基本类型。 (就ARC而言,Core Foundation对象不是Objective-C对象。)您需要阅读内核管理编程指南(适用于Core Foundation)中的“Ownership Policy”“ Core Foundation Object Lifecycle Management” / EM>

  2. “商店的目的地”是CGLayer对象中的实例变量。它不是“通过财产的NSArray”。该值不会“使其进入数组并退出。”数组获取指向CFType对象的指针。该数组保留并释放ledInfo对象。数组永远不会看到或对ledInfo做任何事情。您的ledInfo对象负责保留和释放它想要拥有的任何Core Foundation对象,例如CGLayerRef属性中的图层。

    正如我所提到的,如果ledInfo未在其cg_layer设置器中保留图层(ledInfoCFRetain),则可能会释放图层,带有悬空指针的CGLayerRetain。你明白悬挂指针是什么吗?