Использование памяти растет с CTFontCreateWithName и CTFramesetterRef - PullRequest
5 голосов
/ 13 декабря 2011

Я пишу программу для IOS, в которой используются пользовательские шрифты (CTFontManagerRegisterFontsForURL). Я загружаю шрифт, добавляю его как строковый атрибут, создаю framesetter, затем frame и рисую его в контексте. Я выпускаю все, что я использую. Прибор не обнаруживает утечку, но:

Объем памяти, используемой приложениями, увеличивается и не уменьшается при использовании этой функции. Когда я покидаю функцию, счетчик сохранности моего шрифта равен 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 голосов
/ 14 декабря 2011

retainCount бесполезен. Не называй это.

Если память вашего приложения повторяется, используйте Анализ кучи , чтобы выяснить, что потребляет память. Утечки сообщают только о тех объектах, которые больше недоступны - объектах, адреса которых не отображаются ни в каких активных областях памяти, - и, таким образом, утечки не найдут много видов увеличения памяти.

Это может быть случай кэша только для записи; то есть что-то где-то активно кэширует вещи, но ваш код написан так, что кэшированные копии никогда не извлекаются. Без дополнительной информации - результатов анализа Heapshot, для начинающих - трудно сказать.

<Ч />

Я следовал вашему уроку, и он подтверждает, что постоянная куча рост происходит из-за строки "CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString ((CFAttributedStringRef) строка); ». ОК - вы подтвердили , что просачивается и где оно распределено, но не откуда взято дополнительное удержание. Для этого включите «Record reference count» в инструменте «Allocations» и повторите тест. Это позволит вам проверять обратные следы каждого вызова удержания / освобождения вызывающего объекта. Там будет дополнительное удержание там; удержание не сбалансировано выпуском.

Я предполагаю, что контекст каким-то образом привязан к нему.

(Я уже проанализировал память и увидел, что она занята этот объект, поэтому я проверил сохранение счета.

Абсолютное количество сохраняемых объектов бесполезно. То, что оно все еще находится в памяти, означает, что оно чрезмерно сохранено, и счет сохранения сам по себе не может больше сказать вам ничего, если у вас также не будет полной обратной трассировки каждого отдельного вызова сохранения (и освобождения) объекта, которые инструменты дает вам.

2 голосов
/ 22 июня 2013

Бен, я немного углубился в отсылку к отладчику с устройством iPhone 4, и похоже, что корень проблемы на самом деле в реализации CFMutableAttributedString. Похоже, что происходит, что любой объект, переданный в изменяемую атрибутивную строку с использованием методов CFAttributedStringSetAttribute () или CFAttributedStringSetAttributes (), будет иметь утечку (потому что ссылка будет увеличиваться, но не уменьшаться). Вы видели это с kCTFontAttributeName, но я проверил это, и та же проблема обнаруживается со значением kCTForegroundColorAttributeName или kCTParagraphStyleAttributeName. Например, я проверил память, используемую для объекта стиля абзаца, созданного с помощью CTParagraphStyleCreate () и переданного в строку attr следующим образом:

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

Этот объект paraStyle будет внутренне сохранен attr str, но затем, когда придет время сбросить последний ref в attr str через:

CFRelease(attrString);

Вышеприведенное должно было бы сбросить окончательную ссылку на объект paraStyle, но это не так. Я могу только прийти к одному выводу, это ошибка в реализации Apple изменяемой атрибутивной строки. Также обратите внимание, что я пробовал CFAttributedStringRemoveAttribute () и CFAttributedStringSetAttributes () со фальшивым значением и clearOtherAttributes, установленным в TRUE, но, похоже, ничего не работает, чтобы заставить объект сбросить ссылки на объекты свойств, которые он содержит.

Обновление: после некоторого дополнительного тестирования сегодня я обнаружил, что это минимальный код приложения, необходимый для очень простого воспроизведения утечки. Это позволяет избежать рендеринга текста в контексте, поэтому не может быть проблем с сохранением шрифта ref или чего-то еще. Вам нужны только эти 2 функции в примере делегата приложения:

- (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 продолжает увеличиваться либо с профилем утечек, либо с профилем распределений.

0 голосов
/ 14 мая 2019

теперь это исправлено до тех пор, пока вы выпускаете CTFramesetterRef.

(... и убедитесь, что вы переустановили свое приложение обратно на свое устройство перед повторным запуском Instruments после изменения кода!).

0 голосов
/ 13 декабря 2011

Запускаете ли вы свой код в Instrument (вы его профилируете).

Число сохраняемых объектов не увеличивает использование вашей памяти, это просто говорит о том, что больше объектов заинтересовано в этом конкретном объекте.
Если он освобождается, когда предполагается, что вас это не волнуетфактическое значение сохраняемого количества часто не соответствует ожидаемому, и Apple советует не использовать retainCount в качестве средства отладки из-за этого.Это может дать вам общее представление о том, насколько ваш объект востребован (удерживается другими), но это все.

В инструменте у вас есть инструмент под названием «Утечки», который хорошо подходит для обнаружения утечки памяти.

Я часто видел, что у объекта есть счет сохранения 2, когда я ожидал, что у него будет счет хранения 1, но они были освобождены там, где они должны быть.
Если у вас есть счет сохранения5 в тот момент, когда вы думаете, что он должен быть освобожден, это может указывать на то, что что-то не так, но даже не является гарантией.

...