我有一个与此类似的问题:
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属性并直接从循环工作中访问数组?
答案 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;
}
请注意,在释放旧值之前保留新值非常重要。如果在保留新旧版本之前释放旧版本,并且新版本恰好与旧版本相同,则可以在中间解除分层图层!
回复您的修改:
引用计数适用于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>
“商店的目的地”是CGLayer
对象中的实例变量。它不是“通过财产的NSArray”。该值不会“使其进入数组并退出。”数组获取指向CFType
对象的指针。该数组保留并释放ledInfo
对象。数组永远不会看到或对ledInfo
做任何事情。您的ledInfo
对象负责保留和释放它想要拥有的任何Core Foundation对象,例如CGLayerRef
属性中的图层。
正如我所提到的,如果ledInfo
未在其cg_layer
设置器中保留图层(ledInfo
或CFRetain
),则可能会释放图层,带有悬空指针的CGLayerRetain
。你明白悬挂指针是什么吗?