在iOS 6中,UIStringDrawing方法似乎不是线程安全的

时间:2012-10-05 10:43:34

标签: iphone objective-c ios ios6

同时在两个线程上使用任何UIStringDrawing方法都会导致崩溃。我的理解是所有UIStringDrawing方法都是iOS 4.0中的线程安全的。

此代码(没有任何用处)演示了这个问题:

dispatch_queue_t queue = dispatch_queue_create("com.queue", NULL);

for (int i = 0; i < 10000; i++) {

    dispatch_async(queue, ^{

        NSString *string = @"My string";
        CGSize size = [string sizeWithFont:[UIFont boldSystemFontOfSize:13]];
    });
}

for (int i = 0; i < 10000; i++) {

    NSString *string = @"My string";
    CGSize size = [string sizeWithFont:[UIFont boldSystemFontOfSize:13]];
}

dispatch_release(queue);

应用程序在循环几次迭代后崩溃,并带有以下回溯:

* thread #1: tid = 0x2403, 0x00ad40c8, stop reason = EXC_BAD_ACCESS (code=2, address=0xad40c8)
  frame #0: 0x00ad40c8
  frame #1: 0x36bc4252 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 90
  frame #2: 0x36bc41f2 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 10
  frame #3: 0x38f0368e WebKit`rendererForFont(__GSFont*) + 246
  frame #4: 0x38f03230 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:resultRange:] + 200
  frame #5: 0x38f03162 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:] + 66
  frame #6: 0x38f04532 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:] + 58
  frame #7: 0x361dc5d2 UIKit`-[NSString(UIStringDrawing) sizeWithFont:] + 46
  frame #8: 0x00060ca8 myApp`-[TAViewController drawingTest] + 216 at TAViewController.m:157
  frame #9: 0x38da1e66 Foundation`__NSFireDelayedPerform + 450
  frame #10: 0x3aa47856 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 14
  frame #11: 0x3aa47502 CoreFoundation`__CFRunLoopDoTimer + 274
  frame #12: 0x3aa46176 CoreFoundation`__CFRunLoopRun + 1230
  frame #13: 0x3a9b923c CoreFoundation`CFRunLoopRunSpecific + 356
  frame #14: 0x3a9b90c8 CoreFoundation`CFRunLoopRunInMode + 104
  frame #15: 0x3a8a433a GraphicsServices`GSEventRunModal + 74
  frame #16: 0x3622c288 UIKit`UIApplicationMain + 1120
  frame #17: 0x0005f08c myApp`main + 96 at main.m:16

  thread #5: tid = 0x2a03, 0x00ad40c8, stop reason = EXC_BAD_ACCESS (code=2, address=0xad40c8)
    frame #0: 0x00ad40c8
    frame #1: 0x36bc4252 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 90
    frame #2: 0x36bc41f2 WebCore`WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 10
    frame #3: 0x38f0368e WebKit`rendererForFont(__GSFont*) + 246
    frame #4: 0x38f03230 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:resultRange:] + 200
    frame #5: 0x38f03162 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:forWidth:ellipsis:letterSpacing:] + 66
    frame #6: 0x38f04532 WebKit`-[NSString(WebStringDrawing) _web_sizeWithFont:] + 58
    frame #7: 0x361dc5d2 UIKit`-[NSString(UIStringDrawing) sizeWithFont:] + 46
    frame #8: 0x00060d5c myApp`__31-[TAViewController drawingTest]_block_invoke_0 + 116 at TAViewController.m:150
    frame #9: 0x339f0792 libdispatch.dylib`_dispatch_call_block_and_release + 10
    frame #10: 0x339f3b3a libdispatch.dylib`_dispatch_queue_drain + 142
    frame #11: 0x339f167c libdispatch.dylib`_dispatch_queue_invoke + 44
    frame #12: 0x339f4612 libdispatch.dylib`_dispatch_root_queue_drain + 210
    frame #13: 0x339f47d8 libdispatch.dylib`_dispatch_worker_thread2 + 92
    frame #14: 0x37f957f0 libsystem_c.dylib`_pthread_wqthread + 360
    frame #15: 0x37f95684 libsystem_c.dylib`start_wqthread + 8

我的理解是UIStringDrawing方法在iOS 4中是线程安全的。我希望这些循环应该完整而没有错误。

在运行iOS 6(在iPhone 5上测试)的iPhone上运行时发生崩溃但在运行iOS 5(在iPhone 4上测试)或模拟器(使用iOS 6测试)的iPhone上运行时不会发生崩溃。

我通过使用CGD序列化任何绘制调用来实现我认为的修复:

- (void)serialiseDrawing:(void (^)())block {

    dispatch_sync(self.serialDrawingQueue, block);
}


- (dispatch_queue_t)serialDrawingQueue {

    if (_serialDrawingQueue == NULL) _serialDrawingQueue = dispatch_queue_create("com.myApp.SerialDrawQueue", NULL);

    return _serialDrawingQueue;
}

...并像这样包装每个绘制调用:

__block CGSize labelSize = CGSizeZero;

[[TAUtils sharedUtils] serialiseDrawing:^{
    labelSize = [label.text sizeWithFont:label.font];
}];

这似乎有所改进(我的所有UIStringDrawing调用都发生在一个线程上)。但它有时会因为这样的回溯而崩溃:

Exception Type:  EXC_CRASH (SIGSEGV)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x3a28ee80 semaphore_wait_trap + 8
1   libdispatch.dylib               0x32851e90 _dispatch_thread_semaphore_wait + 8
2   libdispatch.dylib               0x32850680 _dispatch_barrier_sync_f_slow + 100
3   myApp                           0x000c4330 -[TAUtils serialiseDrawing:] (TAUtils.m:305)
4   myApp                           0x000edfd4 -[TAOmniBar updateLabel] (TAOmniBar.m:394)
5   myApp                           0x000ee8d6 -[TAOmniBar handleNotification:] (TAOmniBar.m:461)
6   CoreFoundation                  0x39820346 _CFXNotificationPost + 1418
7   Foundation                      0x37b5838a -[NSNotificationCenter postNotificationName:object:userInfo:] + 66
8   Foundation                      0x37b5be9a -[NSNotificationCenter postNotificationName:object:] + 26
9   myApp                           0x000f369a -[TAMyViewController update] (TAMyViewController.m:1308)
10  GLKit                           0x328383ce -[GLKViewController _updateAndDraw] + 270
11  QuartzCore                      0x39ffd77c CA::Display::DisplayLink::dispatch(unsigned long long, unsigned long long) + 156
12  QuartzCore                      0x39ffd6d4 CA::Display::IOMFBDisplayLink::callback(__IOMobileFramebuffer*, unsigned long long, unsigned long long, unsigned long long, void*) + 60
13  IOMobileFramebuffer             0x31221fd4 IOMobileFramebufferVsyncNotifyFunc + 152
14  IOKit                           0x39f7c5aa IODispatchCalloutFromCFMessage + 190
15  CoreFoundation                  0x39899888 __CFMachPortPerform + 116
16  CoreFoundation                  0x398a43e4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
17  CoreFoundation                  0x398a4386 __CFRunLoopDoSource1 + 134
18  CoreFoundation                  0x398a320a __CFRunLoopRun + 1378
19  CoreFoundation                  0x39816238 CFRunLoopRunSpecific + 352
20  CoreFoundation                  0x398160c4 CFRunLoopRunInMode + 100
21  GraphicsServices                0x39701336 GSEventRunModal + 70
22  UIKit                           0x35089284 UIApplicationMain + 1116
23  myApp                           0x000b806e main (main.m:16)
24  myApp                           0x000b8024 start + 36

Thread 7 name:  Dispatch queue: com.myApp.SerialDrawQueue
Thread 7:
0   WebCore                         0x35a21410 WebCore::FontFallbackList::invalidate(WTF::PassRefPtr<WebCore::FontSelector>) + 156
1   WebCore                         0x35a2124e WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 86
2   WebCore                         0x35a211ee WebCore::Font::Font(WebCore::FontPlatformData const&, WTF::PassRefPtr<WebCore::FontSelector>) + 6
3   WebKit                          0x37d6068a rendererForFont(__GSFont*) + 242
4   WebKit                          0x37d61796 -[NSString(WebStringDrawing) __web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:measureOnly:renderedStringOut:drawUnderline:] + 198
5   WebKit                          0x37d616bc -[NSString(WebStringDrawing) __web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:measureOnly:renderedStringOut:] + 84
6   WebKit                          0x37d6165e -[NSString(WebStringDrawing) __web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:measureOnly:] + 82
7   WebKit                          0x37d61602 -[NSString(WebStringDrawing) _web_drawAtPoint:forWidth:withFont:ellipsis:letterSpacing:includeEmoji:] + 78
8   UIKit                           0x35041960 -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:lineBreakMode:letterSpacing:includeEmoji:] + 172
9   UIKit                           0x3507de1e -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment:includeEmoji:] + 358
10  UIKit                           0x3507dca4 -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment:] + 68
11  myApp                           0x000d3300 -[TALabelManager textureCGImageForString:] (TALabelManager.m:859)
12  myApp                           0x000d350a __39-[TALabelManager textureDataForString:]_block_invoke_0 (TALabelManager.m:875)
13  libdispatch.dylib               0x3284d5d8 _dispatch_client_callout + 20
14  libdispatch.dylib               0x3285080a _dispatch_barrier_sync_f_invoke + 22
15  myApp                           0x000c4330 -[TAUtils serialiseDrawing:] (TAUtils.m:305)
16  myApp                           0x000d3420 -[TALabelManager textureDataForString:] (TALabelManager.m:873)
17  myApp                           0x000d0dde __block_global_0 (TALabelManager.m:516)
18  libdispatch.dylib               0x3284d790 _dispatch_call_block_and_release + 8
19  libdispatch.dylib               0x32850b36 _dispatch_queue_drain + 138
20  libdispatch.dylib               0x3284e678 _dispatch_queue_invoke + 40
21  libdispatch.dylib               0x32851610 _dispatch_root_queue_drain + 208
22  libdispatch.dylib               0x328517d4 _dispatch_worker_thread2 + 88
23  libsystem_c.dylib               0x36df27ee _pthread_wqthread + 358
24  libsystem_c.dylib               0x36df2680 start_wqthread + 4

我为这个长期问题道歉,但对我来说这是一个严重的问题,非常感谢任何帮助。

4 个答案:

答案 0 :(得分:22)

在尝试寻找解决方法时,我注意到iOS 6引入了更广泛的NSAttributedString和Core Text集成,所以我尝试使用NSAttributedString代替NSString交换所有UIStringDrawing方法和等效的NSStringDrawing方法,似乎崩溃已经停止

例如,我现在正在使用:

NSAttributedString *attribStr = [[NSAttributedString alloc] initWithString:@"My String"];
CGSize size = [attribStr size];

而不是:

NSString *str = @"My String";
CGSize size = [str sizeWithFont:font];

答案 1 :(得分:4)

亚当是对的。 UIStringDrawing方法只能在iOS 6上的主队列中安全使用。您可以直接使用NSStringDrawing方法或CoreText从后台队列执行渲染。这是一个已知问题,但可以随意提交更多错误。

答案 2 :(得分:3)

Adam Swinden的解决方案对我有用。这是我如何转换NSString的sizeWithFont:constrainedToSize:

过去是什么:

NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
CGSize size = [text sizeWithFont:font 
               constrainedToSize:(CGSize){width, CGFLOAT_MAX}];

可替换为:

NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
NSAttributedString *attributedText =
    [[NSAttributedString alloc]
        initWithString:text
        attributes:@
        {
            NSFontAttributeName: font
        }];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                           context:nil];
CGSize size = rect.size;

请注意文档提及:

  

在iOS 7及更高版本中,此方法返回小数(大小)   返回的CGRect的组件);使用返回的大小来确定大小   视图,必须使用将其值提高到最接近的更高整数   使用ceil函数。

因此,要拉出用于调整视图大小的计算高度或宽度,我会使用:

CGFloat height = ceilf(size.height);
CGFloat width  = ceilf(size.width);

答案 3 :(得分:1)

基于Adam Swinden和T先生的答案,我写了两个插入式方法:

@implementation NSString (Extensions)

- (CGSize)threadSafeSizeWithFont:(UIFont *)font {
    return [self threadSafeSizeWithFont:font constrainedToSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
}

- (CGSize)threadSafeSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
    // http://stackoverflow.com/questions/12744558/uistringdrawing-methods-dont-seem-to-be-thread-safe-in-ios-6
    NSAttributedString *attributedText =
    [[NSAttributedString alloc]
     initWithString:self
     attributes:@
     {
     NSFontAttributeName: font
     }];
    CGRect rect = [attributedText boundingRectWithSize:size
                                               options:NSStringDrawingUsesLineFragmentOrigin
                                               context:nil];
    return rect.size;
}

@end