J'essaie de construire un FSM pour contrôler un timer en Objective C (iphone sdk). J'ai senti que c'était une étape nécessaire, parce que sinon je me retrouvais avec un code spaghetti contenant des pages d'instructions if-then. La complexité, la non-lisibilité et la difficulté d'ajouter/changer des fonctionnalités m'ont conduit à tenter une solution plus formelle comme celle-ci.
Dans le contexte de l'application, l'état de la minuterie détermine certaines interactions complexes avec les NSManagedObjects, Core Data, etc. J'ai laissé toute cette fonctionnalité de côté pour le moment, afin d'obtenir une vue claire du code FSM.
Le problème est que je ne trouve aucun exemple de ce type de code en Obj-C, et je ne suis pas très sûr de la façon dont je l'ai traduit à partir de l'exemple de code C++ que j'utilisais. (Je ne connais pas du tout le C++, donc il faut deviner.) Je base cette version d'un modèle d'état sur cet article : http://www.ai-junkie.com/architecture/state_driven/tut_state1.html . Je ne fais pas de jeu, mais cet article présente des concepts qui fonctionnent pour ce que je fais.
Pour créer le code (affiché ci-dessous), j'ai dû apprendre beaucoup de nouveaux concepts, notamment les protocoles obj-c, etc. Étant donné que ces concepts sont nouveaux pour moi, tout comme le modèle de conception d'état, j'espère recevoir des commentaires sur cette mise en œuvre. Est-ce ainsi que vous travaillez efficacement avec les objets de protocole dans obj-c ?
Voici le protocole :
@class Timer;
@protocol TimerState
-(void) enterTimerState:(Timer*)timer;
-(void) executeTimerState:(Timer*)timer;
-(void) exitTimerState:(Timer*)timer;
@end
Voici le fichier d'en-tête de l'objet Timer (dans sa forme la plus dépouillée) :
@interface Timer : NSObject
{
id<TimerState> currentTimerState;
NSTimer *secondTimer;
id <TimerViewDelegate> viewDelegate;
id<TimerState> setupState;
id<TimerState> runState;
id<TimerState> pauseState;
id<TimerState> resumeState;
id<TimerState> finishState;
}
@property (nonatomic, retain) id<TimerState> currentTimerState;
@property (nonatomic, retain) NSTimer *secondTimer;
@property (assign) id <TimerViewDelegate> viewDelegate;
@property (nonatomic, retain) id<TimerState> setupState;
@property (nonatomic, retain) id<TimerState> runState;
@property (nonatomic, retain) id<TimerState> pauseState;
@property (nonatomic, retain) id<TimerState> resumeState;
@property (nonatomic, retain) id<TimerState> finishState;
-(void)stopTimer;
-(void)changeState:(id<TimerState>) timerState;
-(void)executeState:(id<TimerState>) timerState;
-(void) setupTimer:(id<TimerState>) timerState;
Et l'implémentation de l'objet Timer :
#import "Timer.h"
#import "TimerState.h"
#import "Setup_TS.h"
#import "Run_TS.h"
#import "Pause_TS.h"
#import "Resume_TS.h"
#import "Finish_TS.h"
@implementation Timer
@synthesize currentTimerState;
@synthesize viewDelegate;
@synthesize secondTimer;
@synthesize setupState, runState, pauseState, resumeState, finishState;
-(id)init
{
if (self = [super init])
{
id<TimerState> s = [[Setup_TS alloc] init];
self.setupState = s;
//[s release];
id<TimerState> r = [[Run_TS alloc] init];
self.runState = r;
//[r release];
id<TimerState> p = [[Pause_TS alloc] init];
self.pauseState = p;
//[p release];
id<TimerState> rs = [[Resume_TS alloc] init];
self.resumeState = rs;
//[rs release];
id<TimerState> f = [[Finish_TS alloc] init];
self.finishState = f;
//[f release];
}
return self;
}
-(void)changeState:(id<TimerState>) newState{
if (newState != nil)
{
[self.currentTimerState exitTimerState:self];
self.currentTimerState = newState;
[self.currentTimerState enterTimerState:self];
[self executeState:self.currentTimerState];
}
}
-(void)executeState:(id<TimerState>) timerState
{
[self.currentTimerState executeTimerState:self];
}
-(void) setupTimer:(id<TimerState>) timerState
{
if ([timerState isKindOfClass:[Run_TS class]])
{
secondTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(currentTime) userInfo:nil repeats:YES];
}
else if ([timerState isKindOfClass:[Resume_TS class]])
{
secondTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(currentTime) userInfo:nil repeats:YES];
}
}
-(void) stopTimer
{
[secondTimer invalidate];
}
-(void)currentTime
{
//This is just to see it working. Not formatted properly or anything.
NSString *text = [NSString stringWithFormat:@"%@", [NSDate date]];
if (self.viewDelegate != NULL && [self.viewDelegate respondsToSelector:@selector(updateLabel:)])
{
[self.viewDelegate updateLabel:text];
}
}
//TODO: releases here
- (void)dealloc
{
[super dealloc];
}
@end
Ne vous inquiétez pas qu'il manque des choses dans cette classe. Elle ne fait rien d'intéressant pour l'instant. Je suis actuellement en train de me battre pour que la syntaxe soit correcte. Actuellement, elle compile (et fonctionne) mais les appels à la méthode isKindOfClass provoquent des avertissements du compilateur (la méthode n'est pas trouvée dans le protocole). Je ne suis pas vraiment sûr de vouloir utiliser isKindOfClass de toute façon. Je pensais donner à chaque id<TimerState>
une chaîne de noms et l'utiliser à la place.
Sur une autre note : tous ces id<TimerState>
étaient à l'origine des déclarations TimerState *. Il semblait logique de les conserver comme propriétés. Je ne suis pas sûr que cela ait un sens avec id<TimerState>
's.
Voici un exemple d'une des classes d'état :
#import "TimerState.h"
@interface Setup_TS : NSObject <TimerState>{
}
@end
#import "Setup_TS.h"
#import "Timer.h"
@implementation Setup_TS
-(void) enterTimerState:(Timer*)timer{
NSLog(@"SETUP: entering state");
}
-(void) executeTimerState:(Timer*)timer{
NSLog(@"SETUP: executing state");
}
-(void) exitTimerState:(Timer*)timer{
NSLog(@"SETUP: exiting state");
}
@end
Encore une fois, jusqu'à présent, il ne fait rien d'autre que d'annoncer dans quelle phase (ou sous-état) il se trouve. Mais ce n'est pas le but.
Ce que j'espère apprendre ici, c'est si cette architecture est composée correctement dans le langage obj-c. Un problème spécifique que je rencontre est la création des objets id dans la fonction init du timer. Comme vous pouvez le voir, j'ai commenté les releases, car ils provoquaient un avertissement "release not found in protocol". Je ne savais pas comment gérer cela.
Ce dont je n'ai pas besoin, c'est de commentaires sur le fait que ce code est exagéré ou qu'il s'agit d'un formalisme inutile, ou quoi que ce soit d'autre. Cela vaut la peine que je l'apprenne même si ces idées sont vraies. Si cela peut vous aider, considérez-le comme une conception théorique d'un FSM en obj-c.
Merci d'avance pour tout commentaire utile.
(cela n'a pas beaucoup aidé : Machine à états finis en Objective-C )