使用CTFontCreateWithName和CTFramesetterRef会导致内存使用量增加。

5
我正在编写一款使用自定义字体(CTFontManagerRegisterFontsForURL)的IOS程序。我加载字体,将其添加为字符串属性,创建框架设置器,然后创建框架,并将其绘制到上下文中。我释放了我所使用的所有内容。仪器没有发现泄漏,但是:

使用此函数时,应用程序使用的内存会增加,并且不会缩小。

当我离开该函数时,我的字体保留计数为2。

以下是代码:

CFMutableAttributedStringRef attributedStringRef = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringBeginEditing(attributedStringRef);
CFAttributedStringReplaceString(attributedStringRef, CFRangeMake(0, 0), (CFStringRef)label.text);

font = CTFontCreateWithName((CFStringRef)label.fontName, label.fontHeight, NULL);

字体的保留计数为:1

CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, label.text.length), kCTFontAttributeName, font);
CFAttributedStringEndEditing(attributedStringRef);

字体的保留计数为2。

CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, rect);

CFRelease(font);

字体的保留计数:1

CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(attributedStringRef); 

字体的保留计数为:3

CFRelease(attributedStringRef);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter,
                                            CFRangeMake(0, 0),
                                            path, NULL);

字体的保留计数:5

CFRelease(frameSetter);

字体的保留计数为4。

CTFrameDraw(frame, ctx);
CFRelease(frame);

字体的保留计数: 2

CGPathRelease(path);

有一种缓存吗?我真的需要立即清除此字体使用的内存。

P.S:我使用CFGetRetainCount获取字体的保留计数。

谢谢!

4个回答

4

retainCount是无用的,请勿调用。

如果您的应用程序的内存重复增长,请使用Heapshot分析来确定哪些内容占用了内存。Leaks仅报告不再可达的对象,即地址未出现在任何活动内存区域中的对象,因此,Leaks将无法找到许多种类的内存增加。

这可能是写入缓存的情况,即某个地方主动缓存东西,但您的代码编写方式使得缓存的副本从未被检索出来。没有附加信息,例如Heapshot分析的结果,很难说。


我按照您的教程进行了操作,并确认永久堆增长是由行“CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string); "引起的。 好的 - 您已经确认了正在泄漏和分配位置,但不知道额外的保留源自何处。为此,请在分配工具中打开“记录参考计数”并重新运行测试。 这将允许您检查有关有害对象的每个保留/释放调用的回溯。那里会有一个额外的保留;一个未平衡的保留。

我猜上下文与之相关。

(我已经分析了内存并看到它被这个对象占用,这就是为什么我检查了保留计数。

对象的绝对保留计数无用。它仍然在内存中意味着它被过度保留,除非您也拥有关于对象的每个单独保留(和释放)调用的完整回溯,否则保留计数本身不能告诉您任何更多信息,而Instruments可以提供这些信息。


我按照你的教程操作,确认了永久堆增长是由这行代码引起的:“CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string);”。(我已经分析了内存并发现它被该对象占用,这就是我检查保留计数的原因。) - Ben

3

Ben,我使用调试器和iPhone 4设备对impl进行了深入分析,看起来问题的根源实际上在于CFMutableAttributedString的实现。似乎正在发生的情况是,使用CFAttributedStringSetAttribute()或CFAttributedStringSetAttributes()方法将任何对象传递到可变属性字符串中时都会泄漏(因为引用将被增加但不会减少)。你之前看到的是使用kCTFontAttributeName,但我测试了一下,使用kCTForegroundColorAttributeName或kCTParagraphStyleAttributeName值也会出现同样的问题。例如,我检查了通过CTParagraphStyleCreate()创建并通过attr str传递的段落样式对象所使用的内存:

CTParagraphStyleRef  paragraphStyle = CTParagraphStyleCreate(paragraphSettings, 1);  
CFRange textRange = CFRangeMake(0, [self length]);
CFAttributedStringSetAttribute(mAttributedString, textRange, kCTParagraphStyleAttributeName, paragraphStyle);
CFRelease(paragraphStyle);

这个paragraphStyle对象将被attr str内部保留,但是当通过以下方式放弃对attr str的最后一个引用时:

CFRelease(attrString);

上面的代码应该已经删除了对paragraphStyle对象的最后一个引用,但事实并非如此。我只能得出一个结论,这是苹果可变属性字符串实现中的一个错误。注意,我还尝试使用虚假值和clearOtherAttributes设置为TRUE的CFAttributedStringRemoveAttribute()和CFAttributedStringSetAttributes(),但似乎没有任何东西可以强制对象删除其持有的属性对象的引用。
更新:在今天进行了一些额外的测试之后,我发现这是在非常简单的方式下复制泄漏所需的最少应用程序代码。这避免了将文本呈现到上下文中,因此不能是与上下文保存字体引用等相关的问题。您只需要在应用程序委托示例中使用这两个函数即可:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
  // Override point for customization after application launch.
  self.window.backgroundColor = [UIColor whiteColor];
  [self.window makeKeyAndVisible];

  [self.timer invalidate];
  self.timer = [NSTimer timerWithTimeInterval: 0.5
                                       target: self
                                     selector: @selector(timerCallback:)
                                     userInfo: NULL
                                      repeats: TRUE];

  [[NSRunLoop currentRunLoop] addTimer:self.timer forMode: NSDefaultRunLoopMode];

  return YES;
}

// This callback is invoked onver and over on an interval. The goal of this function is to demonstrate
// a memory leak in CoreText. When a font is set with CFAttributedStringSetAttribute() and then
// the mutable string is copied by CTFramesetterCreateWithAttributedString(), the memory associated
// with the font ref is leaked.

- (void) timerCallback:(NSTimer*)timer
{
  CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

  CFStringRef cfStr = (CFStringRef)@"a";
  CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), cfStr);

  CFRange range = CFRangeMake(0, 1);

  CTFontRef plainFontRef = CTFontCreateWithName((CFStringRef)@"Helvetica", 12, nil);

  // plainFontRef retain count incremented from 1 to 2

  CFAttributedStringSetAttribute(attrString, range, kCTFontAttributeName, plainFontRef);

  // plainFontRef retain count incremented from 2 to 4. Note that in order to see
  // a leak  this CTFramesetterCreateWithAttributedString() must be invoked. If
  // the creation of a framesetter is commented out, then the font inside the
  // attr string would be dellocated properly. So, this is likely a bug in the
  // implementation of CTFramesetterCreateWithAttributedString() in how it copies
  // properties from the mutable attr string.

  CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);

  // plainFontRef retain count decremented from 4 to 3 (note that it should have been decremented by 2)

  CFRelease(framesetter);

  // retain count is 1 at this point, so attrString is deallocated. Note that this should
  // drop the retain count of the font ref but it does not do that.

  CFRelease(attrString);

  // The retain count here should be 1 and this invocation should drop the last ref.
  // But the retain count for plainFontRef is 3 at this point so the font leaks.

  CFRelease(plainFontRef);

  return;
}

我已在模拟器(iOS 5和6)以及运行iOS 5.1的设备上进行了测试,并且在所有情况下都发现了泄漏。有没有人可以在iOS 6或更新版本中尝试一下,看看是否也会出现泄漏,关键是CTFont对象的数量会随着泄漏配置文件或分配配置文件而不断增加。


1

现在已经修复了,只要您释放CTFramesetterRef即可。

(......并确保在代码更改后重新安装应用程序到设备上,然后再运行Instruments!)。


我必须相信你,因为这个项目已经不再存在了! - Ben

0

你在仪器中运行了你的代码吗(你对其进行了分析)。

对象的保留计数不会增加你的内存使用量,它只是说明更多的对象对该特定对象感兴趣。
如果它在应该被释放时被释放,你不需要关心保留计数的实际值,通常它不是你所期望的,而且苹果建议不要将retainCount用作调试工具。它可以让你大致了解你的对象有多受欢迎(被其他对象保留),但仅此而已。

在仪器中,你有一个名为“泄漏”的工具,它很擅长查找内存泄漏。

我经常看到对象的保留计数为2,而我原本以为它们的保留计数为1,但它们在应该被释放的地方被释放了。
如果在你认为它应该被释放的时候,它的保留计数为5,那可能表明有些问题,但也不能完全保证。


我在Instruments中使用了内存分配工具和内存泄漏工具来运行它。我没有发现任何内存泄漏,但是通过分配工具,我可以看到我的字体仍然被分配了(并且我的应用程序内存使用量更大)。我猜测还有一些东西在保留它,但是是什么?为什么?我该如何强制释放它? - Ben
从你的代码来看,我会说 ctx 是为了它自己的使用而保留的。你在内存中只有一个吗?还是在运行代码时它们会不断增加? - Vincent Bernier
如果是通过 UIGraphicsBeginImageContext 获取 ctx,然后再使用 UIGraphicsGetCurrentContext 方法获取 ctx,在调用我的方法之后,最后使用 UIGraphicsGetImageFromCurrentImageContext 和 UIGraphicsEndImageContext 方法获取图像。 - Ben
如果您转到另一个屏幕,或者在删除该“图像”时,您的字体是否仍然存在于Instrument中? - Vincent Bernier
是的!!这就是为什么我认为我的字体可能在某种缓存或其他地方。 - Ben
你之前的做法是有问题的,因为如果向下钻取视图并不一定会使其被释放。这就是我能提供的全部帮助了,我的字体知识不是很好,而且我更习惯于在Cocoa框架中进行内存管理,而不是在核心图形库中。希望有人能给你一个更好的答案。 - Vincent Bernier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接