【问题标题】:Memory usage grows with CTFontCreateWithName and CTFramesetterRef内存使用量随 CTFontCreateWithName 和 CTFramesetterRef 增长
【发布时间】:2012-01-19 11:33:17
【问题描述】:

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

使用此功能时,应用程序使用的内存会增长,不会缩小。 当我离开函数时,我的字体的保留计数是 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 来获取字体的保留计数。

谢谢!

【问题讨论】:

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


    【解决方案1】:

    您是否在 Instrument 中运行您的代码(您是否对其进行了概要分析)。

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

    在仪器中,您有一个名为“Leaks”的工具,可以很好地发现内存泄漏。

    我经常看到对象的保留计数为 2,而我原本期望它们的保留计数为 1,但它们被释放到它们应该在的位置。
    如果在您认为应该将其释放之前的保留计数为 5,则可能表明有问题,但甚至不能保证。

    【讨论】:

    • 我在 Instruments 中使用内存分配工具和内存泄漏工具运行它。我没有任何内存泄漏,但是,使用分配工具,我可以看到我的字体仍然被分配(并且我的应用程序内存使用量更大)。我的猜测是某些东西仍在保留它,但是什么?为什么 ?以及如何强制释放?
    • 从您的代码中我想说ctx 保留它以供个人使用。您是否只有一个在内存中,或者在您运行代码时它们会堆积起来?
    • ctx 如果以这种方式获得:UIGraphicsBeginImageContext,然后是 UIGraphicsGetCurrentContext,然后我调用我的方法,然后是 UIGraphicsGetImageFromCurrentImageContext 和 UIGraphicsEndImageContext。
    • 如果您转到另一个屏幕,或者当您删除该“图像”时,您的字体是否仍在 Instrument 中?
    • 是的!这就是为什么我认为我的字体可能在某种缓存或其他东西中
    【解决方案2】:

    retainCount 没用。不要调用它。

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

    这可能是只写缓存的情况;即某处的某些东西正在主动缓存东西,但是您的代码是这样编写的,因此永远不会检索到缓存的副本。如果没有额外的信息——对于初学者来说,Heapshot Analysis 的结果——很难说。


    我按照您的教程进行操作,它确认永久堆 增长是由于“CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string); ”。 好的——您已经确认 what 正在泄漏以及它在哪里分配,但没有确认额外保留的来源。为此,在分配工具中打开“记录引用计数”并重新运行测试。这将允许您检查对违规对象的每个保留/释放调用的回溯。那里会有一个额外的保留;一个没有被释放平衡的保留。

    我猜上下文在某种程度上取决于它。

    (我已经分析过内存,发现它被 这个对象,这就是我检查保留计数的原因。

    对象的绝对保留计数是无用的。它仍在内存中意味着它被过度保留,并且保留计数本身无法真正告诉您更多信息,除非您还拥有对该对象的每个保留(和释放)调用的完整回溯,其中 Instruments给你。

    【讨论】:

    • 我按照您的教程进行操作,它确认永久堆增长是由于行“CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string); ”。 (我已经分析了内存,发现它被这个对象占用了,这就是我检查保留计数的原因。
    【解决方案3】:

    Ben,我用调试器和 iPhone 4 设备对 impl 进行了深入研究,看起来问题的根源实际上在于 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 在内部保留,但是当需要通过以下方式将最后一个 ref 删除到 attr str 时:

    CFRelease(attrString);
    

    上面应该已经将最终的 ref 删除到了 paragraphStyle 对象,但它没有。我只能得出一个结论,这是 Apple 实现可变属性字符串中的一个错误。另请注意,我尝试使用虚假值和 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 对象的数量随着泄漏配置文件或分配配置文件的增加而不断增加。

    【讨论】:

      【解决方案4】:

      只要您释放CTFramesetterRef,此问题现已得到修复。

      (...并确保在代码更改后重新运行 Instruments 之前将您的应用重新安装回您的设备!)。

      【讨论】:

      • 我必须相信你,因为这个项目已经不存在了!
      猜你喜欢
      • 2013-08-27
      • 1970-01-01
      • 2017-11-09
      • 2013-04-17
      • 1970-01-01
      • 2013-10-08
      • 2014-12-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多