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

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

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

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上testing)的iPhone上运行时发生崩溃,但在运行iOS 5(在iPhone 4上testing)或模拟器(在iOS 6上testing)的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 

我为长期的问题表示抱歉,但是这对我来说是一个严重的问题,并且非常感谢您的帮助。

虽然试图find一个解决办法,我注意到,iOS 6引入更广泛的NSAttributedString和核心文本的集成,所以我试着用NSAttributedString代替NSString交换所有的UIStringDrawing方法与等价的NSStringDrawing方法,似乎崩溃已经停止。

例如,我现在使用:

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

代替:

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

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

亚当·斯温登的解决scheme为我工作。 以下是我如何转换NSString的sizeWithFont:constrainedToSize: ::

曾经是:

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

可以replace为:

 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); 

根据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