2010-07-08 7 views
1

[mise à jour: ce problème a été résolu; le problème ne se trouvait pas dans drawInRect: mais dans UIGraphicsBeginImageContext()]Doit drawInRect: pour qu'un contexte séparé soit exécuté sur le thread principal?

Dans mon application, j'attrape un tas de grandes images, les recadrant en taille de vignette et en stockant les vignettes pour prévisualisation.

Notez que je le fais dans un contexte d'image distinct - c'est pas à propos de redessiner un UIView qui est sur l'écran.

Ce code est plutôt intensif, donc je le lance dans un thread séparé. La mise à l'échelle réelle ressemble à ceci, et est une mise en œuvre de la catégorie au-dessus de UIImage:

- (UIImage *) scaledImageWithWidth:(CGFloat)width andHeight:(CGFloat)height 
{ 
    CGRect rect = CGRectMake(0.0, 0.0, width, height); 
    UIGraphicsBeginImageContext(rect.size); 
    [self drawInRect:rect]; // <-- crashing on this line 
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 
    return scaledImage; 
} 

Ceci est appelé à partir d'une méthode distincte, qui fait une boucle à travers les images à son tour et fait le traitement. L'appel réel à la méthode ci-dessus ressemble à ceci:

UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f]; 

Tout cela fonctionne la plupart du temps, mais parfois je reçois un EXC_BAD_ACCESS.

Backtrace:

#0 0x330d678c in ripc_RenderImage() 
#1 0x330dd5aa in ripc_DrawImage() 
#2 0x300e3276 in CGContextDelegateDrawImage() 
#3 0x300e321a in CGContextDrawImage() 
#4 0x315164c8 in -[UIImage drawInRect:blendMode:alpha:]() 
#5 0x31516098 in -[UIImage drawInRect:]() 
#6 0x0000d6e4 in -[UIImage(Scaling) scaledImageWithWidth:andHeight:] (self=0x169320, _cmd=0x30e6e, width=48, height=64) at /Users/me/Documents/svn/app/trunk/Classes/UIImage+Scaling.m:20 
#7 0x00027df0 in -[mgMinimap loadThumbnails] (self=0x13df00, _cmd=0x30d05) at /Users/me/Documents/svn/app/trunk/Classes/mgMinimap.m:167 
#8 0x32b15bd0 in -[NSThread main]() 
#9 0x32b81cfe in __NSThread__main__() 
#10 0x30c8f78c in _pthread_start() 
#11 0x30c85078 in thread_start() 

[mise à jour 4] Quand je lance ce dans le simulateur, et ce problème se produit, la console affiche en outre les éléments suivants:

// the below is before loading the first thumbnail 
<Error>: CGContextSaveGState: invalid context 
<Error>: CGContextSetBlendMode: invalid context 
<Error>: CGContextSetAlpha: invalid context 
<Error>: CGContextTranslateCTM: invalid context 
<Error>: CGContextScaleCTM: invalid context 
<Error>: CGContextDrawImage: invalid context 
<Error>: CGContextRestoreGState: invalid context 
<Error>: CGBitmapContextCreateImage: invalid context 
// here, the first thumbnail has finished loading and the second one 
// is about to be generated 
<Error>: CGContextSetStrokeColorWithColor: invalid context 
<Error>: CGContextSetFillColorWithColor: invalid context 

Mon sentiment profond est que je Parfois, il finit par essayer de drawInRect: alors que le système d'exploitation essaye aussi de dessiner quelque chose, ce qui résulte, de temps en temps, dans un accident. J'ai toujours supposé que tant que vous ne dessinez pas sur l'écran, c'est acceptable - n'est-ce pas le cas? Ou si c'est le cas, une idée de ce qui pourrait en être la cause?

Mise à jour (r2): J'ai oublié de mentionner que cette application fonctionne sous des contraintes de mémoire assez sévères (j'ai beaucoup d'images chargées à un moment donné et elles sont permutées), donc cela peut être un cas de manque de mémoire (lire - ce n'est pas). Je ne suis pas sûr de savoir comment vérifier cela, cependant, donc les pensées sur ce serait la bienvenue aussi. J'ai vérifié cela en réduisant fortement le nombre d'images chargées et en ajoutant une vérification pour s'assurer qu'elles sont correctement désallouées (elles le sont, et le crash se produit toujours).

Mise à jour 3: Je pensais avoir trouvé le problème. Voici la réponse que j'écrit, avant l'accident est arrivé à nouveau:

Le code serait (après que je posté cette question) commencent à sortir de temps en temps avec le code de sortie 0, et parfois avec le code de sortie 10 (SIGBUS). 0 signifie "aucune erreur", ce qui était extrêmement étrange. 10 semble signifier un peu de tout ce qui était inutile aussi. L'appel drawInRect: était un gros indice, cependant, quand l'accident est arrivé là-bas. Le passage en boucle pour obtenir les vignettes générait beaucoup d'images auto-libérées. J'avais une piscine autorelease mais elle enveloppait toute la boucle. J'ai ajouté une deuxième piscine autorelease dans la boucle for:

- (void)loadThumbnails 
{ 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    for (...) { 
     NSAutoreleasePool *cyclePool = 
      [[NSAutoreleasePool alloc] init]; // <-- here 
     UIImage *bigger = ...; 
     UIImage *small = [bigger scaledImageWithWidth:24.f andHeight:32.f]; 
     UIImage *bloated = [i scaledImageWithWidth:48.f andHeight:64.f]; 
     [cyclePool release]; // <-- ending here 
    } 
    [pool release]; 
} 

Je pensais que ce qui précède fixe la question, jusqu'à ce que je courais l'application et se sont écrasés avec moi « code de sortie 0 » à nouveau juste plus tôt. Retour à la planche à dessin...

Répondre

1

Il se trouve, il y a deux réponses: « Doit drawInRect: un contexte distinct être exécuté sur le thread principal »

La réponse est non, ce n'est pas le cas. Cependant, UIGraphicsBeginImageContext doit. C'est en fait la raison de l'écrasement qui s'est produit. Le crash ne s'est pas révélé jusqu'à ce que le contexte graphique (invalide) soit modifié, c'est pourquoi le crash s'est produit sur la ligne drawInRect:.

La solution est de cesser d'utiliser UIGraphicsContext et au lieu d'utiliser CGBitmapContext, qui est thread-safe.

+3

À partir de iOS4.0 Le dessin dans un contexte graphique dans UIKit est désormais sécurisé pour les threads. Plus précisément: Les routines utilisées pour accéder et manipuler le contexte graphique peuvent désormais gérer correctement les contextes résidant sur des threads différents. Ref: http://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS4.html#//apple_ref/doc/uid/TP40009559-SW29 – MattyG

+0

Merci pour la trouvaille, MattyG. Définitivement intéressant. – Kalle

+0

@Kalle: alors quel code avez-vous changé? pouvez-vous partager s'il vous plaît? –

0

J'ai l'impression que vous ne devriez pas appeler directement drawInRect car il ne peut pas être thread-safe. Vous devriez appeler setNeedsDisplay sur la vue qui enverra un message à drawInRect quand il est sécuritaire de le faire.

+0

Si vous regardez le code, vous remarquerez que c'est impossible car je dessine le rect dans un contexte d'image séparé. – Kalle

+0

En regardant les tutoriels Stanford iTunes U, qui ont été donnés par des employés d'Apple, ils ont explicitement déclaré que vous n'appelez jamais drawRect directement. Je ne sais pas si la même chose s'applique à drawInRect. –

+0

Ce n'est pas le cas. Il y a environ 5 exemples Apple qui font cet appel. Un exemple est ici (du code d'exemple de livre de cuisine): http://developer.apple.com/iphone/library/samplecode/iPhoneCoreDataRecipes/Listings/Classes_RecipeDetailViewController_m.html – Kalle

2

Avez-vous regardé la dernière version de Matt Gemmell, MGImageUtilities? J'extrait ce de sa source sur GitHub:

// Create appropriately modified image. 
UIImage *image; 
UIGraphicsBeginImageContextWithOptions(destRect.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen". 
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here. 
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. 
[image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically. 
CGImageRelease(sourceImg); 
image = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext(); 

Je ne sais pas si la sécurité des threads est la question, mais il peut être intéressant d'essayer le code de Matt avant d'aller trop loin dans cette voie.

+0

Intéressant. Jettera un coup d'oeil. Merci pour le conseil! – Kalle

+0

J'ai regardé dans ce plus proche, et j'ai même essayé de le faire de la même manière (je l'ai déjà été, mais), mais pas de chance. Même appel (drawInRect :), même erreur. – Kalle

Questions connexes