【问题标题】:Bug in UIKit string drawing method?UIKit 字符串绘制方法中的错误?
【发布时间】:2012-07-21 05:33:27
【问题描述】:

为了复制我在应用程序中遇到的崩溃,我必须创建一个重复率略高的示例,这可能不切实际,但可以准确地展示我的应用程序中发生的情况。在使用NSOperations 在后台线程上绘制 NSString 时,有时会发生崩溃,崩溃前堆栈跟踪上的最后一次调用是WebCore::FontFallbackList::~FontFallBackList().

- (void)viewDidLoad
{
    queue = [[NSOperationQueue alloc] init];
    [NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:@selector(timerDidFire:) userInfo:nil repeats:YES];
}

-(void)timerDidFire:(NSTimer*)timer
{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        CGRect rect = CGRectMake(0, 0, 50, 50);
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(rect.size.width, rect.size.height), YES, 0.0);
        NSString *string = @"Sd";
        [string drawInRect:rect withFont:[UIFont boldSystemFontOfSize:12] lineBreakMode:UILineBreakModeTailTruncation]; 
        UIGraphicsEndImageContext();     
    }];
    [queue addOperation:op];
}

您可以使用上面的代码轻松复制此崩溃。任何人都对这次崩溃的性质以及为什么会发生有任何见解? (这个问题的解决方法是设置[queue setMaxConcurrentOperations:1];

【问题讨论】:

  • 如果你在块外(即在主线程上)实例化字体怎么办?
  • 刚试了一下,还是没区别。
  • 作为替代方案,如果我用 Core Text 绘制这个字符串,我就不会再崩溃了。最终可能会这样做。
  • 向 Apple 提交错误,看看他们对此有何看法?
  • @maq .. 你有没有找到任何解决方法,因为我遇到了同样的问题

标签: iphone objective-c ios


【解决方案1】:

这似乎是 iOS 5.x 中的回归:它发生在 5.0 和 5.1 模拟器和 5.1 设备上,但不会在 4.3 模拟器或 4.3.2 设备上发生。

特别是字符串绘制似乎被破坏了——如果你所做的只是字符串绘制(避免创建/销毁上下文的开销),崩溃几乎立即发生:

-(void)threadFunc:(UIFont *)font {
  @autoreleasepool {
    NSString *string = @" ";
    CGRect r = {{0,0},{50,50}};
    UIGraphicsBeginImageContextWithOptions(r.size, YES, 0);
    for(;;) {
      @autoreleasepool {
        [string drawAtPoint:r.origin withFont:font];
      }
    }
    UIGraphicsEndImageContext();
  }
}

-(void)startThreads
{
  UIFont * font = [UIFont systemFontOfSize:12];
  for (int i = 2; i--;)
  {
    [NSThread detachNewThreadSelector:@selector(threadFunc:) toTarget:self withObject:font];
  }
}

编辑:它仅在多核环境中“几乎即时”(即双核设备或模拟器,假设是多核 Mac)。否则,崩溃大约需要 10-20 分钟。我没有双核 iOS 4.x 设备(唯一的可能性似乎是 iPad 2),但一个多小时后单核设备没有崩溃。

我向 Apple 提出了一个错误,如果它影响到你,我鼓励你也这样做。

【讨论】:

  • 有趣..关于核心文本和多线程的任何想法?在后台线程上绘图时,文档似乎已经过时。我用 Core Text 绘制文本没有任何问题,但文档确实说(我认为)框架设置器和其他一些类型的对象一次只能在一个线程上使用..
  • @mohabitar 这通常意味着每个 instance 一次只能在一个线程上使用——在不同的线程上使用不同的 CTFramesetter 应该是安全的。但是,线程安全有时是微妙的(尤其是当您想避免不必要的锁定时),问题通常很少见且难以追踪,如果根本提到线程安全,文档通常是错误的。
【解决方案2】:

一个简单的解决方法是用 NSAttributedString 替换 NSString。

@maq 列出的崩溃测试如果像这样修改不会崩溃:

-(void)threadFunc:(UIFont *)font {
    @autoreleasepool {
        NSString *string = @" ";
        NSMutableAttributedString *test = [[NSMutableAttributedString alloc] initWithString:string];
        CGRect r = {{0,0},{50,50}};
        UIGraphicsBeginImageContextWithOptions(r.size, YES, 0);
        for(;;) {
            @autoreleasepool {
                 UIFont *font=[UIFont fontWithName:@"Helvetica-Bold" size:30.0f];
                [test addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, test.length)];
                [test drawAtPoint:r.origin];
            }
        }
        UIGraphicsEndImageContext();
    }
}

【讨论】:

  • 我应该再补充一件事:有时你会在从并发队列中绘制属性字符串时在模拟器中看到与内存相关的崩溃——相同的代码在我拥有的任何实际设备上运行良好经过测试(在 iPad 4 上启用了僵尸并随着时间的推移进行了分析)。我最终在我的代码中添加了一个模拟器检查(基于设备/模拟器的并发/串行队列)。
【解决方案3】:

我不相信 UIKit 是线程安全的,而且从后台线程绘制非常时髦。 它可能有效,也可能无效。

UIGraphicsBeginContext 的文档实际上明确指出您应该只从主线程调用它。显然,多线程完全不在讨论范围内。

【讨论】:

  • 根据What's New in iOS 4.0,所有这些函数现在应该可以安全地在多个线程中使用。否则 CATiledLayer 会有点难以使用。
  • 是的,我在上面的代码中所做的确切事情是在 WWDC 2012 会议视频中完成的,所以我确信它应该是安全的。
  • UIGraphicsBeginImageContextWithOptions 应该是线程安全的。 UIFont 不是。那么,我认为您的堆栈跟踪在 FontFallbackList 中显示问题不一定是巧合。
  • @isaac:我链接的文档特别说“现在可以安全地在多个线程中使用颜色和字体对象”。
猜你喜欢
  • 2021-11-11
  • 1970-01-01
  • 1970-01-01
  • 2013-11-14
  • 2012-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-03
相关资源
最近更新 更多