30 votes

Comment réaliser une machine à états finis de base en Objective-C

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 )

15voto

Jarrod Roberson Points 32263

Je suggère d'utiliser Compilateur de machines à états il produira Objectif-C code. J'ai eu de bons résultats en Java et Python en utilisant cette méthode.

Vous ne devriez pas écrire le code de la machine d'état à la main, vous devriez utiliser quelque chose qui génère le code pour vous. Le SMC génère un code propre et clair que vous pouvez ensuite examiner si vous souhaitez en tirer des enseignements, ou vous pouvez simplement l'utiliser et en finir avec lui.

8voto

jlehr Points 9809

Lorsque vous utilisez un protocole comme modificateur de type, vous pouvez fournir une liste de protocoles séparés par des virgules. Ainsi, tout ce que vous devez faire pour vous débarrasser de l'avertissement du compilateur est d'ajouter NSObject à la liste de protocoles comme suit :

- (void)setupTimer:(id<TimerState,NSObject>) timerState {

    // Create scheduled timers, etc...
}

7voto

Blake Watters Points 5330

Si vous voulez une implémentation très simple, en Objective-C, d'une machine à états, je viens de publier le document suivant TransitionKit qui fournit une API bien conçue pour la mise en œuvre des machines à états. Elle est testée en profondeur, bien documentée, très facile à utiliser et ne nécessite aucune génération de code ni aucun outil externe.

3voto

Kamek Points 1365

Je vous suggère de vérifier Statec Il y a un joli petit dsl pour faire des FSM et sortir du code ObjC. C'est un peu comme mogenerator pour les machines à états.

1voto

Warren P Points 23750

Je suis assez novice en Objective-C, mais je vous suggère de regarder l'implémentation ANSI C de la State Machine.

Ce n'est pas parce que vous utilisez Cocoa que vous devez utiliser les messages Objective-C ici.

En ANSI C, l'implémentation d'une machine à états peut être très simple et lisible.

Ma dernière implémentation en C d'un FSM spécifié #define STATE_x ou énumérer les types pour les états et avoir une table de pointeurs vers les fonctions pour exécuter chaque état.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X