2010-06-03 4 views
2

Je souhaite effectuer une animation sur le thread principal (car les objets UIKit ne sont pas thread-safe), mais le préparer dans un thread séparé. J'ai (baAnimation - est CABasicAnimation attribué & inited avant):Exécution de sélecteurs sur le thread principal avec NSInvocation

SEL animationSelector = @selector(addAnimation:forKey:); 
NSString *keyString = @"someViewAnimation"; 

NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[workView.layer methodSignatureForSelector:animationSelector]]; 
[inv setTarget:workView.layer]; 
[inv setSelector:animationSelector]; 
[inv setArgument:baAnimation atIndex:2]; 
[inv setArgument:keyString atIndex:3]; 
[inv performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:NO]; 

je reçois:

*** + [longueur NSCFString]: sélecteur non reconnu envoyé à la classe 0x1fb36a0

Appels:

>  #0 0x020984e6 in objc_exception_throw 
>  #1 0x01f7e8fb in +[NSObject doesNotRecognizeSelector:] 
>  #2 0x01f15676 in ___forwarding___ 
>  #3 0x01ef16c2 in __forwarding_prep_0___ 
>  #4 0x01bb3c21 in -[CALayer addAnimation:forKey:] 
>  #5 0x01ef172d in __invoking___ 
>  #6 0x01ef1618 in -[NSInvocation invoke] 

Mais [workView.layer addAnimation:baAnimation forKey:@"someViewAnimation"]; fonctionne très bien. Qu'est-ce que je fais mal?

Répondre

6

En plus de [inv retArguments] (comme mentionné par Chris Suter), vous devez également passer les arguments en tant que pointeurs vers la mémoire sous-jacente. Citant l'API:

« Lorsque la valeur d'argument est un objet, passer un pointeur vers la variable (ou mémoire) à partir de laquelle doit être copié l'objet:

NSArray *anArray; 
[invocation setArgument:&anArray atIndex:3]; 

»

+3

Cela devrait être la réponse correcte acceptée, car c'est le plus gros problème (ne pas passer le bon truc du tout, par opposition à un problème de gestion de la mémoire) – user102008

2

Vous avez besoin soit d'ajouter [inv retainArguments] ou modifier le paramètre waitUntilDone YES, mais avant de faire cela, permettez-moi de dire que ce que vous avez fait est assez illisible.

Ce que je voudrais faire est de stocker tout ce que l'état dont vous avez besoin dans les variables d'instance et puis quand vous êtes prêt, il suffit de faire:

[self performSelectorOnMainThread:@selector (startAnimation) withObject:nil waitUntilDone:NO];

allocation et initialisant également un CABasicAnimation sur un fil est inutile (il ne prendra pas de temps notable pour le faire sur le fil principal), et est toujours potentiellement dangereux. Conservez un travail intensif sur un thread séparé, mais pas sur autre chose.

+0

Merci beaucoup. Vraiment, je suis tout à fait d'accord avec vous et cette tâche a des objectifs "théoriques" (juste pour essayer de cette façon). – kpower

+0

@Chris Suter - Réponse est bonne, mais le reste du conseil va à l'encontre de la sagesse acceptée sur le filetage. Il est de loin, beaucoup plus facile de passer des paramètres à des entrées et sorties bien définies entre les threads que d'essayer de synchroniser l'accès aux ressources partagées. Ce n'est pas seulement moi qui parle. Voici les conseils d'Apple sur le sujet http://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW6 – DougW

+0

@ DougW Je pense que cela dépend de la situation.En règle générale, vous avez raison, mais l'objectif numéro un devrait être de maximiser la lisibilité et je pense qu'une erreur commune que beaucoup de programmeurs font est de suivre aveuglément des modèles ou des règles. Dans cette situation, et il est difficile de dire exactement sans en savoir plus sur le problème, je soupçonne que l'utilisation de variables d'instance pour enregistrer l'état va être plus simple et plus facile à maintenir que de faire ce que vous suggérez. –

4

Si vous avez un ou plusieurs arguments dans votre NSInvocation alors je recommanderais de créer une nouvelle catégorie qui appelle le sélecteur sur le thread principal. Voilà comment je résolu ce problème:

Exemple
NSInvocation + MainThread.h

#import <Foundation/Foundation.h> 

@interface NSInvocation (MainThread) 
- (void)invokeOnMainThreadWithTarget:(id)target; 
@end 

NSInvocation + MainThread.m

#import "NSInvocation+MainThread.h" 

@implementation NSInvocation (MainThread) 

- (void)invokeOnMainThreadWithTarget:(id)target { 
    [self performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:target waitUntilDone:YES]; 
} 

@end 
+0

Excellente idée, merci –

Questions connexes