内存使用量随CTFontCreateWithName和CTFramesetterRef而增长

时间:2011-12-13 15:35:06

标签: iphone objective-c ios fonts core-text

我正在编写一个使用自定义字体(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 个答案:

答案 0 :(得分:4)

retainCount没用。不要打电话。

如果您的应用内存以可重复的方式增长,请使用Heapshot Analysis来确定消耗内存的内容。泄漏仅报告不再可访问的对象 - 其地址未出现在内存的任何活动区域中的对象 - 因此,泄漏将不会发现多种内存增加。

这可能是只写高速缓存的情况;即某些地方主动缓存内容,但您的代码编写时不会检索缓存副本。没有其他信息 - 初学者的快照分析结果 - 很难说。


  

我按照你的教程,它确认了永久堆   增长是由于行“CTFramesetterRef frameSetter =   CTFramesetterCreateWithAttributedString((CFAttributedStringRef)字符串);   ”。   好的 - 您已经确认正在泄漏什么以及分配的位置,而不是额外保留的来源。为此,在分配工具中打开“记录参考计数”并重新运行测试。这将允许您检查违规对象上每个保留/释放调用的回溯。那里会有额外的保留;保留与释放不平衡。

我猜测背景是以某种方式挂在它上面。

  

(我已经分析了记忆,看到它被占用了   这个对象,这就是我检查保留计数的原因。

对象的绝对保留计数是无用的。它仍然在内存中意味着它被过度保留并且保留计数本身不能真正告诉你任何更多内容,除非你还拥有对象上每个保留(和释放)调用的完全回溯给你。

答案 1 :(得分:2)

Ben,我使用调试器和iPhone 4设备进行了深入研究,看起来问题的根源实际上是在CFMutableAttributedString实现中。看起来发生的事情是,使用CFAttributedStringSetAttribute()或CFAttributedStringSetAttributes()方法传递到可变属性字符串的任何对象都将泄漏(因为ref将递增但不递减)。您使用kCTFontAttributeName看到它,但我测试了它,同样的问题也出现了kCTForegroundColorAttributeName或kCTParagraphStyleAttributeName值。例如,我检查了用于通过CTParagraphStyleCreate()创建的段落样式对象的内存,并将其传递到attr str,如下所示:

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

此段落样式对象将由attr str在内部保留,但是当时间到了将最后一个引用放到attr str时:

CFRelease(attrString);

上面应该将最终引用删除到paragraphStyle对象,但它没有。我只能得出一个结论,这是Apple实现可变属性字符串的一个错误。另请注意,我尝试使用假名值CFAttributedStringRemoveAttribute()和CFAttributedStringSetAttributes()并将clearOtherAttributes设置为TRUE,但似乎没有任何东西可以强制对象将refs删除到它所拥有的属性对象。

更新:经过今天的一些额外测试后,我发现这是以非常简单的方式重现泄漏所需的最小应用程序代码。这样可以避免将文本渲染到上下文中,因此保存字体引用或其他内容时不会出现问题。您只需在app delegate示例中使用以下两个函数:

- (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对象的数量随着泄漏配置文件或分配配置文件而不断增加。

答案 2 :(得分:0)

您是否在仪器中运行了代码(让您对其进行分析)。

对象的保留计数不会增加您的内存使用量,只是说明更多对象对该特定对象感兴趣。
如果它被假定为你不关心保留计数的实际值而被解除分配,通常它不是你所期望的,并且Apple建议不要使用retainCount作为调试工具,因为这样。它可以让您大致了解您的对象需求量(由其他人保留),但就是这样。

在仪器中,您有一个工具调用“泄漏”,擅长发现内存泄漏。

我经常看到对象的保留计数为2,当我期望它们的保留计数为1时,但是它们被取消分配给它们。 如果你认为它应该被解除分配之前的保留计数为5,那么这可能表明出现了问题,甚至没有保证。

答案 3 :(得分:0)

现在,只要您松开CTFramesetterRef,此问题就得到解决。

(...,并确保在代码更改后重新运行Instruments之前,将应用重新安装到设备上!)