在 iPhone 上通过 CoreText 使用带有某些字体的 Unicode 文本时出现内存泄漏

发布于 2024-10-02 08:35:56 字数 1783 浏览 3 评论 0原文

我在 iPhone 上遇到了一些有趣的 CoreText 问题,在某些情况下会导致内存泄漏。

我在文档和互联网上到处查看,似乎没有人遇到同样的问题。然而,情况可能比较特殊(见下文)。

不管怎样,经过大量缩小范围后,我设法得到了这个重现案例:

void leakTest(NSString* fontname, NSString* text)
{
    NSDictionary* descriptorAttr = [NSDictionary dictionaryWithObjectsAndKeys:
        fontname, (const NSString*)kCTFontFamilyNameAttribute, nil];

    CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)descriptorAttr);
    CTFontRef           font       = CTFontCreateWithFontDescriptor(descriptor, 0, nil);

    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
        (id)font, (const NSString*)kCTFontAttributeName, nil];

    CFRelease(descriptor);
    CFRelease(font);

    NSMutableAttributedString* string = [[NSMutableAttributedString alloc] initWithString:text];
    [string setAttributes:dict range:NSMakeRange(0, string.length)];

    CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFMutableAttributedStringRef)string);

    CFRelease(typesetter);
    [string release];
}

根据字体和文本,根据泄漏工具,这可能会也可能不会泄漏 - 各种/众多的东西,但绝对看起来与字体和字体描述符相关。

这些可能是由以下原因引起的:

const char* unicodeText = "Ernle\310\235e"; // "Ernleȝe" if your editor groks unicode 

NSString* textLeaky  = [NSString stringWithUTF8String:unicodeText];
NSString* textNormal = @"Hello World";

leakTest(@"Courier", textNormal);   // This doesn't leak
leakTest(@"Courier", textLeaky);    // This does leak
leakTest(@"Arial",   textLeaky);    // This doesn't leak with this font?

显然,我注释掉了,以便留下一个泄漏测试调用来使用泄漏工具进行测试!

因此,带有不寻常但完全合法的 unicode 字符的字符串会导致一种字体泄漏,但不会导致另一种字体泄漏。

我的代码是否有问题,而我碰巧使用 Arial “侥幸逃脱”?我想知道是否可能存在某种字体缓存导致了明显的泄漏,但操作系统或其他任何东西实际上都可以正常处理它。

希望可以帮到大家!

I'm having some interesting issues with CoreText on iPhone, causing memory leaks in certain circumstances.

I've looked everywhere in the documentation and across the internet and no-one seems to be getting the same issue. However, the circumstances are perhaps special (see below).

Anyway, after a lot of narrowing down, I managed to get this repro case:

void leakTest(NSString* fontname, NSString* text)
{
    NSDictionary* descriptorAttr = [NSDictionary dictionaryWithObjectsAndKeys:
        fontname, (const NSString*)kCTFontFamilyNameAttribute, nil];

    CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)descriptorAttr);
    CTFontRef           font       = CTFontCreateWithFontDescriptor(descriptor, 0, nil);

    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
        (id)font, (const NSString*)kCTFontAttributeName, nil];

    CFRelease(descriptor);
    CFRelease(font);

    NSMutableAttributedString* string = [[NSMutableAttributedString alloc] initWithString:text];
    [string setAttributes:dict range:NSMakeRange(0, string.length)];

    CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((CFMutableAttributedStringRef)string);

    CFRelease(typesetter);
    [string release];
}

Depending on the font and text, this may or may not leak according to the Leaks tool - various/numerous things but definitely seeming font and font descriptor related.

These can be caused as follows:

const char* unicodeText = "Ernle\310\235e"; // "Ernleȝe" if your editor groks unicode 

NSString* textLeaky  = [NSString stringWithUTF8String:unicodeText];
NSString* textNormal = @"Hello World";

leakTest(@"Courier", textNormal);   // This doesn't leak
leakTest(@"Courier", textLeaky);    // This does leak
leakTest(@"Arial",   textLeaky);    // This doesn't leak with this font?

Obviously I commented out so as to leave one leakTest call to test with the Leaks tool!

So, the string with an unusual but perfectly legal unicode character in causes a leak with one font but not the other.

Is there something wrong in my code and I happen to be "getting away with it" with Arial? I wondered whether there may be some kind of font caching that was causing the apparent leak but the OS or whatever is actually all handling it okay.

Hope you can help folks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你丑哭了我 2024-10-09 08:35:56

尽管被泄漏的对象是 CTFont,但我敢打赌 NSMutableAttributedString 正在泄漏。尝试添加一个 @"bogus" 属性,例如一个空的 NSData 对象,看看它是否也被泄漏。

Although the object being leaked is a CTFont, I would bet NSMutableAttributedString is doing the leaking. Try adding a @"bogus" attribute with, say, an empty NSData object and see if it is also leaked.

-残月青衣踏尘吟 2024-10-09 08:35:56

看看这段代码(没有内存泄漏),我使用不同的字体和 unicode 文本。
当我使用 (CFMutableAttributedStringRef) 字符串构造时出现内存泄漏。

我认为你必须使用 CFAttributedStringCreateMutable() 来消除泄漏。

CTTextAlignment alignment = kCTLeftTextAlignment;
CTLineBreakMode breakMode = kCTLineBreakByTruncatingTail;
CGFloat paragraphSpacing = 0;
CGFloat maximumLineSpacing = 0;

CTParagraphStyleSetting paragraphStyleSetting[] = {
    {kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment},
    {kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &breakMode},
    {kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), ¶graphSpacing},
    {kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &maximumLineSpacing}
};

CTParagraphStyleRef paragraphStyleRef = CTParagraphStyleCreate(paragraphStyleSetting, sizeof(paragraphStyleSetting) / sizeof(paragraphStyleSetting[0]));

CFMutableAttributedStringRef attributedStringRef = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
if (attributedStringRef) {
    CFAttributedStringReplaceString(attributedStringRef, CFRangeMake(0, 0), (CFStringRef)aText);
    CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, aText.length), kCTFontAttributeName, aFontRef);
    CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, aText.length), kCTForegroundColorAttributeName, aColorRef);
    CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, aText.length), kCTParagraphStyleAttributeName, paragraphStyleRef);

    CFRelease(paragraphStyleRef);

    CTTypesetterRef typesetterRef = CTTypesetterCreateWithAttributedString(attributedStringRef);
    //skip
    CFRelease(typesetterRef);

    CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(attributedStringRef);
    CFRelease(attributedStringRef);

    if (framesetterRef) {
    //skip
        CFRelease(frameRef);
    }
}

look at this code (no memory leaks) and I use different fonts and unicode text.
There were memory leaks when I use (CFMutableAttributedStringRef)string counstruction.

I think you have to use CFAttributedStringCreateMutable() to remove leaks.

CTTextAlignment alignment = kCTLeftTextAlignment;
CTLineBreakMode breakMode = kCTLineBreakByTruncatingTail;
CGFloat paragraphSpacing = 0;
CGFloat maximumLineSpacing = 0;

CTParagraphStyleSetting paragraphStyleSetting[] = {
    {kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment},
    {kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &breakMode},
    {kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), ¶graphSpacing},
    {kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &maximumLineSpacing}
};

CTParagraphStyleRef paragraphStyleRef = CTParagraphStyleCreate(paragraphStyleSetting, sizeof(paragraphStyleSetting) / sizeof(paragraphStyleSetting[0]));

CFMutableAttributedStringRef attributedStringRef = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
if (attributedStringRef) {
    CFAttributedStringReplaceString(attributedStringRef, CFRangeMake(0, 0), (CFStringRef)aText);
    CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, aText.length), kCTFontAttributeName, aFontRef);
    CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, aText.length), kCTForegroundColorAttributeName, aColorRef);
    CFAttributedStringSetAttribute(attributedStringRef, CFRangeMake(0, aText.length), kCTParagraphStyleAttributeName, paragraphStyleRef);

    CFRelease(paragraphStyleRef);

    CTTypesetterRef typesetterRef = CTTypesetterCreateWithAttributedString(attributedStringRef);
    //skip
    CFRelease(typesetterRef);

    CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(attributedStringRef);
    CFRelease(attributedStringRef);

    if (framesetterRef) {
    //skip
        CFRelease(frameRef);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文