2010-01-22 4 views
0

Je dois peindre une image en utilisant certaines données et la montrer sur mon application iphone. Comme la peinture prend beaucoup de temps (2-3 secondes sur l'appareil), je veux peindre sur un fil différent. Aussi, je veux pouvoir annuler la peinture, changer quelque chose dans les données et recommencer. Il est donc préférable pour moi d'utiliser NSOperation. Maintenant, quand je dessine sur le fil principal, tout va bien.
Lorsque je fais exactement la même chose avec la sous-classe NSOperation, tout semble correct, mais seulement 95% du temps. Parfois, il ne dessine pas l'image complète. Parfois, il ne dessine pas de texte. Parfois, il utilise des couleurs différentes, il pourrait y avoir des points rouges/vert/bleu dispersés sur l'image, etc etc etcProblèmes lors de l'utilisation de Quartz 2D sur NSOperation


J'ai fait un exemple très courte pour illustrer ceci: D'abord, nous faisons tout le tableau sur un thread principal dans une méthode régulière:

//setting up bitmap context 
size_t width = 400; 
size_t height = 400; 
size_t bitsPerComponent = 8; 
size_t bytesPerRow = 4 * width; 
void* imageData = malloc(bytesPerRow * height); 
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
CGContextRef context = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast); 
CFRelease(colorSpace); 

//transforming it to usual coordinate system 
CGRect mapRect = CGRectMake(0, 0, width, height); 
UIGraphicsPushContext(context); 
CGContextTranslateCTM(context, 0, mapRect.size.height); 
CGContextScaleCTM(context, 1, -1); 

//actull drawing - nothing complicated here, 2 lines and 3 text strings on white background 
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); 
CGContextFillRect(context, mapRect); 

CGContextSetLineWidth(context, 3); 
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor); 

CGContextMoveToPoint(context, 10, 10); 
CGContextAddLineToPoint(context, 20, 20); 
CGContextStrokePath(context); 

CGContextMoveToPoint(context, 20, 20); 
CGContextAddLineToPoint(context, 100, 100); 
CGContextStrokePath(context); 

[UIColor blackColor].set; 
[[NSString stringWithString:@"tag1"] drawInRect:CGRectMake(10, 10, 40, 15) withFont:[UIFont systemFontOfSize:15]]; 
[[NSString stringWithString:@"tag2"] drawInRect:CGRectMake(20, 20, 40, 15) withFont:[UIFont systemFontOfSize:15]]; 
[[NSString stringWithString:@"tag3"] drawInRect:CGRectMake(100, 100, 40, 15) withFont:[UIFont systemFontOfSize:15]]; 

//getting UIImage from bitmap context 
CGImageRef _trueMap = CGBitmapContextCreateImage(context); 
if (_trueMap) { 
    UIImage* _map = [UIImage imageWithCGImage:_trueMap]; 
    CFRelease(_trueMap); 
    //displaying what we got 
    //self.map leads to UIImageView 
    self.map = _map; 
} 

//releasing context and memmory 
UIGraphicsPopContext(); 
CFRelease(context); 
free(imageData); 

Pas d'erreur ici. Fonctionne toujours


Maintenant, je vais sous-classe NSOperation et copier-coller là ce code: Interface:

@interface Painter : NSOperation { 
//The controller which contains UIImageView we will use to display image 
MapViewController* mapViewController; 

CGContextRef context; 
void* imageData; 
} 

@property (nonatomic, assign) MapViewController* mapViewController; 

- (id) initWithRootController:(MapViewController*)mvc__; 

@end 

Maintenant, les méthodes:

- (id) initWithRootController:(MapViewController*)mvc__ { 
if (self = [super init]) { 
    self.mapViewController = mvc__; 
    size_t width = 400; 
    size_t height = 400; 
    size_t bitsPerComponent = 8; 
    size_t bytesPerRow = 4 * width; 
    imageData = malloc(bytesPerRow * height); 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
    context = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast); 
    CFRelease(colorSpace); 
} 
return self; 
} 
- (void) main { 
size_t width = 400; 
size_t height = 400; 

//transforming it to usual coordinate system 
CGRect mapRect = CGRectMake(0, 0, width, height); 
UIGraphicsPushContext(context); 
CGContextTranslateCTM(context, 0, mapRect.size.height); 
CGContextScaleCTM(context, 1, -1); 

//actull drawing - nothing complicated here, 2 lines and 3 text strings on white background 
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); 
CGContextFillRect(context, mapRect); 

CGContextSetLineWidth(context, 3); 
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor); 

CGContextMoveToPoint(context, 10, 10); 
CGContextAddLineToPoint(context, 20, 20); 
CGContextStrokePath(context); 

CGContextMoveToPoint(context, 20, 20); 
CGContextAddLineToPoint(context, 100, 100); 
CGContextStrokePath(context); 

[UIColor blackColor].set; 
[[NSString stringWithString:@"tag1"] drawInRect:CGRectMake(10, 10, 40, 15) withFont:[UIFont systemFontOfSize:15]]; 
[[NSString stringWithString:@"tag2"] drawInRect:CGRectMake(20, 20, 40, 15) withFont:[UIFont systemFontOfSize:15]]; 
[[NSString stringWithString:@"tag3"] drawInRect:CGRectMake(100, 100, 40, 15) withFont:[UIFont systemFontOfSize:15]]; 

//getting UIImage from bitmap context 
CGImageRef _trueMap = CGBitmapContextCreateImage(context); 
if (_trueMap) { 
    UIImage* _map = [UIImage imageWithCGImage:_trueMap]; 
    CFRelease(_trueMap); 
    //displaying what we got 
    [mapViewController performSelectorOnMainThread:@selector(setMap:) withObject:_map waitUntilDone:YES]; 
} 

//releasing context and memmory 
UIGraphicsPopContext(); 
CFRelease(context); 
free(imageData); 
} 

Encore une fois, aucun changement de code significatif entre ce 2 morceaux de code. Et quand je commence cette opération comme ceci:

NSOperationQueue* repaintQueue = [[NSOperationQueue alloc] init]; 
repaintQueue.maxConcurrentOperationCount = 1; 
[repaintQueue addOperation:[[[Painter alloc] initWithRootController:self] autorelease]]; 

Cela fonctionnera. Mais pas toujours, parfois l'image contiendra des artefacts.


J'ai aussi fait quelques captures d'écran pour illustrer la question, mais n'a pas pu les poster = ( Quoi qu'il en soit, il y a une capture d'écran qui montre la ligne rouge et 3 lignes de texte (ce qui est bien) et une capture d'écran qui montre la ligne rouge, pas de lignes de texte et "tag2" écrit à l'envers sur le contrôleur de la barre d'onglets

Alors, quel est le problème? Je ne peux pas utiliser Quartz avec NSOperation? Y at-il une sorte de restriction sur le dessin sur des fils séparés? il y a un moyen de contourner ces restrictions si c'est le cas Si quelqu'un a déjà vu ce problème, veuillez répondre à:

Répondre

1

Quelques appels dans votre code sont sous forme UIKit (ceux UI préfixés), et UIKit n'est pas threadsafe. Toutes les opérations de l'interface utilisateur doivent être appelées sur le thread principal, ou vous risquez des choses étranges qui se produisent, comme l'artéfact.

Je ne peux pas parler de Quartz2d (ou Core Grahics) lui-même, car je n'en ai pas beaucoup utilisé directement. Mais je sais que UIKit n'est pas threadsafe.

La méthode drawInRect: withFont: provient d'une catégorie ajoutée à NSString à partir de UIKit (UIStringDrawing). Ces méthodes ne sont pas threadsafe, et c'est pourquoi vous voyez un comportement étrange.

+0

Cette explication semble très plausible, cependant, je ne peux toujours pas le faire fonctionner. Je l'ai fait appeler drawInRect: WithFont: sur le thread principal, mais cela n'a pas aidé.J'ai remplacé ceci: [[NSString stringWithString: @ "tag1"] drawInRect: CGRectMake (10, 10, 40, 15) avecFont: [UIFont systemFontOfSize: 15]]; Avec ceci: [self performSelectorOnMainThread: @selector (drawText :) withObject: @ "tag1" waitUntilDone: OUI]; , où drawText: est - (void) drawText: (NSString *) text__ {[texte__ drawInRect: CGRectMake (20, 20, 40, 15) withFont: [UIFont systemFontOfSize: 15]]; } D'autres conseils s'il vous plaît? – Alexey

+0

J'ai oublié de mentionner que j'ai également déplacé imageWithCGImage: sur le fil principal. – Alexey

+0

Ok, je l'ai eu le travail après avoir remplacé tous les appels UIKit avec des appels CG. Toujours aucune idée de comment le faire fonctionner avec n'importe quel appel UIKit. – Alexey

Questions connexes