2012-10-05 44 views
22

在兩個線程上同時使用任何UIStringDrawing方法會導致崩潰。我的理解是所有的UIStringDrawing方法都是iOS 4.0的線程安全。UIStringDrawing方法在iOS中似乎不是線程安全的6

此代碼(什麼都不做任何使用)說明此問題:

* 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 

我的理解是:

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

環路與以下回溯幾個迭代後的應用程序崩潰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 

我爲長的問題道歉,但這對我來說是一個嚴重的問題,真的希望任何幫助。

+0

我沒看完你的整個問題,但你知道所有的繪圖應該總是在主線程上完成,對嗎?沒有一個是線程安全的。 –

+3

從[技術問答QA1637:CATiledLayer和UIKit圖形](http://developer.apple.com/library/ios/#qa/qa1637/_index.html):>重要提示:從iOS 4.0開始,繪製到圖形上下文在UIKit中是線程安全的。這包括訪問和操作當前圖形堆棧,繪製圖像和字符串以及從輔助線程使用顏色和字體對象。 –

+0

我不知道。很高興學到新的東西 –

回答

22

雖然試圖找到解決辦法,我注意到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]; 
+0

我正在使用[t drawAtPoint:point withFont:font],我如何使用它 - 或者任何其他方法 - 如果我用NSAttributedString替換NSString? – user836026

+0

@ user836026 [NSAttributedString UIKit Additions](http://developer.apple.com/library/ios/#documentation/uikit/reference/NSAttributedString_UIKit_Additions/Reference/Reference.html)提供了一個'drawAtPoint:'方法。您不需要指定字體,因爲它將包含在帶有NSFontAttributeName的NSAttributedString中。 –

+0

我遇到了一個類似的問題,在後臺線程上使用'UILabel'的時候,我得到了整個WebCore :: FontFallbackList :: invalidate(WTF :: PassRefPtr )錯誤。當我停止使用'-adjustsFontSizeToFitWidth'時,錯誤消失了。我最終不得不手動調整標籤的字體大小。 :( –

4

亞當是正確的。 UIStringDrawing方法只能從iOS 6的主隊列中使用。您可以直接使用NSStringDrawing方法或CoreText從後臺隊列中執行渲染。這是一個已知的問題,但隨時可以提出更多的錯誤。

3

亞當斯溫登的解決方案爲我工作。下面是如何轉換的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並且稍後,此方法返回小數大小(返回的CGRe的大小爲 組件CT);若要使用返回的大小來調整 的視圖,則必須使用ceil函數將其值提高到最接近的較大整數 。

於是拔出計算的高度或寬度用於上漿的意見,我會用:

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

但這不適用於iOS 6,對嗎? – Raptor

+1

這是iOS6的工作原理,我把這個同樣的答案轉貼到另一個問題上,我給出了更詳細的解釋。 /一個/588253分之18951386 –

1

基於亞當Swinden的和T先生的答案我寫了2下拉式的方法:

@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 
相關問題