2012-10-05 25 views
22

Al utilizar cualquier método UIStringDrawing en dos subprocesos al mismo tiempo se produce un bloqueo. Según entendí, todos los métodos de UIStringDrawing estaban protegidos contra subprocesos desde iOS 4.0.Los métodos UIStringDrawing no parecen ser seguros para subprocesos en iOS 6

Este código (que no hace nada de cualquier uso) muestra el problema:

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

la aplicación se bloquea después de unas pocas iteraciones de los bucles con la siguiente traza:

* 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 

Tenía entendido que los métodos UIStringDrawing eran seguros para hilos desde iOS 4. Espero que estos bucles se completen sin errores.

El bloqueo se produce cuando se ejecuta en un iPhone con iOS 6 (probado en iPhone 5) pero NO ocurre cuando se ejecuta en un iPhone con iOS 5 (probado en iPhone 4) o el simulador (probado con iOS 6).

he implementado lo que pensaba que era un arreglo por serialising cualquier sorteo de llamadas usando 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; 
} 

... y envolver cada sorteo llamar así:

__block CGSize labelSize = CGSizeZero; 

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

Esto parece tener cosas mejoradas un poco (todas mis llamadas UIStringDrawing ocurren en un hilo). Sin embargo, todavía se colgará en ocasiones con una traza de esta manera:

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 

Me disculpo por la pregunta larga, pero esto es un problema serio para mí y agradecería cualquier ayuda.

+0

No leí toda su pregunta, pero usted sabe que todo el dibujo siempre debe hacerse en el hilo principal, ¿no? Nada de eso es seguro para subprocesos. –

+3

De [Preguntas técnicas QA1637: gráficos CATiledLayer y UIKit] (http://developer.apple.com/library/ios/#qa/qa1637/_index.html):> Importante: a partir de iOS 4.0, dibujar en un contexto de gráficos en UIKit es seguro para subprocesos. Esto incluye el acceso y la manipulación de la pila de gráficos actual, el dibujo de imágenes y cadenas, y el uso de colores y objetos de fuentes de subprocesos secundarios. –

+0

No lo sabía. Es bueno aprender algo nuevo –

Respuesta

22

Mientras trataba de encontrar un trabajo, noté que iOS 6 presenta una integración mucho más generalizada de NSAttributedString y Core Text, así que traté de intercambiar todos los métodos UIStringDrawing con los métodos NSStringDrawing equivalentes utilizando NSAttributedString en lugar de NSString y parece que los bloqueos detenido.

Por ejemplo, ahora estoy usando:

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

en lugar de:

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

Estoy usando [t drawAtPoint: point withFont: font], ¿cómo podría usarlo, o cualquier otro método, si reemplazo NSString con NSAttributedString? – user836026

+0

@ user836026 [NSAttributedString UIKit Additions] (http://developer.apple.com/library/ios/#documentation/uikit/reference/NSAttributedString_UIKit_Additions/Reference/Reference.html) proporciona un método 'drawAtPoint:'. No necesita especificar la fuente, ya que se incluirá en 'NSAttributedString' con' NSFontAttributeName'. –

+0

Me encontré con un problema similar en el que recibí el error 'WebCore :: FontFallbackList :: invalidate (WTF :: PassRefPtr )' al usar 'UILabel's en una cadena de fondo. El error desapareció cuando dejé de usar '-adjustsFontSizeToFitWidth'. Terminé teniendo que dimensionar manualmente la fuente para la etiqueta. :( –

4

Adam es correcta. Los métodos de UIStringDrawing solo se pueden utilizar de forma segura desde la cola principal en iOS 6. Puede utilizar los métodos de NSStringDrawing o CoreText directamente para realizar la representación desde las colas de fondo. Este es un problema conocido, pero no dude en presentar más errores.

3

La solución de Adam Swinden funcionó para mí.Así es como me convertí de NSString sizeWithFont:constrainedToSize::

Lo que solía ser:

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

se puede sustituir por:

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; 

Tenga en cuenta la documentación menciones:

En iOS 7 y más tarde, este método devuelve tamaños fraccionarios (en el componente de tamaño del CGRe devuelto) Connecticut); para usar un tamaño devuelto al tamaño de vistas, debe usar elevar su valor al entero más alto más cercano usando la función ceil.

Así que para sacar la altura calculada o que pueda ser usada para el dimensionamiento de puntos de vista, me gustaría utilizar:

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

pero esto no funciona con iOS 6, ¿no? – Raptor

+1

Esto sí funciona en iOS6. Publiqué esta misma respuesta a otra pregunta. Doy una explicación más detallada allí. Eche un vistazo: http://stackoverflow.com/a/18951386/588253 –

1

Sobre la base de las respuestas del Sr. T Adán Swinden y escribí 2 métodos de abandono en:

@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 
Cuestiones relacionadas