Memory usage grows with CTFontCreateWithName and CTFramesetterRef
Asked Answered
S

4

5

I'm writing an IOS program which uses custom fonts (CTFontManagerRegisterFontsForURL). I load the font, add it as a string attribute, create a framesetter, then a frame, and draw it to a context. I release everything i use. Instruments doesn't notice a leak but :

The memory used by the applications grows and doesn't shrink when using this function. The retain count of my font is 2 when i leave the function.

Here is the code :

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

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

retain count of the font : 1

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

retain count of the font : 2

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

CFRelease(font);

retain count of the font : 1

CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(attributedStringRef); 

retain count of the font : 3

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

retain count of the font : 5

CFRelease(frameSetter);

retain count of the font : 4

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

retain count of the font : 2

CGPathRelease(path);

Is there some sort of cache ? I really need to flush the memory used by this font immediately.

P.S : I used CFGetRetainCount to get the retain count of the font.

Thanks !

Shovelhead answered 13/12, 2011 at 15:35 Comment(0)
I
1

this has now been fixed as long as you release the CTFramesetterRef.

(... and make sure you reinstall your app back to your device before re-running Instruments after code changes!).

Information answered 14/5, 2019 at 19:4 Comment(1)
I'll have to trust you on this one since the project is no longer alive !Shovelhead
C
4

retainCount is useless. Don't call it.

If your app's memory is growing in a repeatable fashion, use Heapshot Analysis to figure out what is consuming memory. Leaks only reports objects that are no longer reachable -- objects whose address does not appear in any active regions of memory -- and, thus, leaks will not find many kinds of memory accretion.

This may be a case of a write-only cache; i.e. something somewhere is proactively caching stuff, but your code is written such that the cached copies are never retrieved. Without additional information -- the results of Heapshot Analysis, for starters -- it is hard to say.


I followed your tutorial, and it confirms that the permanent heap growth is due to the line "CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string); ". OK -- you've confirmed what is leaking and where it is allocated, but not where the extra retain comes from. To that, turn on "Record reference counts" in the Allocations instrument and re-run the test. This will allow you to inspect the backtraces of every retain/release call on the offending object. There will be an extra retain in there; a retain not balanced by a release.

I'm guessing the context is somehow hanging on to it.

(I had already analyzed the memory and saw that it was occupied by this object, that's why i checked retain count.

The absolute retain count of an object is useless. That it is still in memory means that it is over-retained and the retain count, itself, can't really tell you anything more unless you also have the full backtrace of every single retain (and release) call on the object, which Instruments gives you.

Clay answered 14/12, 2011 at 17:49 Comment(1)
I followed your tutorial, and it confirms that the permanent heap growth is due to the line "CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string); ". (I had already analyzed the memory and saw that it was occupied by this object, that's why i checked retain count.Shovelhead
F
3

Ben, I did some deep diving into the impl with the debugger with and iPhone 4 device and it looks like the root of the problem is actually in the CFMutableAttributedString implementation. It looks like what is going on is that any object passed into an mutable attributed string using the CFAttributedStringSetAttribute() or CFAttributedStringSetAttributes() methods will leak (because the ref will be incremented but not decremented). You were seeing it with a kCTFontAttributeName, but I tested it and the same problem shows up with a kCTForegroundColorAttributeName or kCTParagraphStyleAttributeName value as well. For example, I examined the memory used for a paragraph style object created via CTParagraphStyleCreate() and passed into the attr str like so:

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

This paragraphStyle object would be preserved internally by the attr str, but then when it came time to drop the last ref to the attr str via:

CFRelease(attrString);

The above should have dropped the final ref to the paragraphStyle object, but it does not. I can only come to one conclusion, this is a bug in Apple's implementation of the mutable attributed string. Note also that I tried CFAttributedStringRemoveAttribute() and CFAttributedStringSetAttributes() with a phony value and the clearOtherAttributes set to TRUE but nothing seems to work to force the object to drop the refs to the property objects it holds.

Update: after some additional testing today, I found that this is the minimal app code needed to reproduce the leak in a very simple way. This avoids ever rendering the text into a context, so it cannot be a problem with the context saving the font ref or something. You only need these 2 functions in an app delegate example:

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

I have tested this in the simulator (iOS 5 and 6) and on a device with iOS 5.1 and I see the leak in all cases. Could someone with iOS 6 or newer try this out and see if the leak appears there also, the key is that the number of CTFont objects keeps increasing either with the leaks profile or the allocations profile.

Flight answered 22/6, 2013 at 8:42 Comment(0)
I
1

this has now been fixed as long as you release the CTFramesetterRef.

(... and make sure you reinstall your app back to your device before re-running Instruments after code changes!).

Information answered 14/5, 2019 at 19:4 Comment(1)
I'll have to trust you on this one since the project is no longer alive !Shovelhead
A
0

Have you run your code in Instrument (have you profile it).

The retain count of an object don't grows your memory usage it's just stating that more object are interested in that particular object.
If it get deallocated when it's suppose to be you don't care about the actual value of the retain count, often it's not what you are expecting, and Apple advise to not use the retainCount as a debugging tool because of that. It can give you a general idea of how much your object is in demand (retain by others) but that's it.

In instrument you have a tool call "Leaks" that is good at finding memory leak.

I often have seen object have a retain count of 2, when I was expecting them to have a retain count of 1, but they were deallocated where they were suppose to be.
If you have a retain count of 5 at the point just before you think it should get deallocated, that could be an indication that something is wrong, but not even a guaranty.

Adenine answered 13/12, 2011 at 16:17 Comment(6)
I ran it in Instruments, with Memory Allocation tool, and Memory Leaks tool. I don't get any memory leak, but, with the allocation tool, i can see that my font is still allocated (and my app memory usage is greater). My guess is that something is still retaining it, but what ? why ? and how can i force that releasing ?Shovelhead
from your code I would say that ctx is retaining it for it's personal use. Do you have only one in memory or while you run your code they are pilling up?Adenine
ctx if obtained this way : UIGraphicsBeginImageContext, then UIGraphicsGetCurrentContext, then i call my method, and then i UIGraphicsGetImageFromCurrentImageContext and UIGraphicsEndImageContext.Shovelhead
if you go to an other screen, or when you remove that 'image' is your font still around in Instrument?Adenine
Yes !! That's why i think my font may be in a sort of cache or something elseShovelhead
...You were moving backward, because if drilling down the view won't necessarily get deallocated. That is as far as I can go with you, my knowledge about font is not that good, and I'm more use to memory management in the Cocoa framework than in the core graphics one. I hope some one will have a better answer for you.Adenine

© 2022 - 2024 — McMap. All rights reserved.