Comment faire une Machine D'état fini de base dans L'objectif-C
je tente de construire un FSM pour contrôler un minuteur dans (iphone sdk) objectif C. J'ai pensé que c'était une étape nécessaire, parce que je finissais autrement avec un code spaghetti méchant contenant des pages de déclarations if-then. La complexité, la non-lisibilité et la difficulté d'ajouter/modifier des caractéristiques m'amènent à tenter une solution plus formelle comme celle-ci.
dans le contexte de la demande, l'état du minuteur détermine certaines interactions complexes avec NSManagedObjects, Core Data, et ainsi de suite. J'ai abandonné toutes ces fonctionnalités pour l'instant, dans une tentative d'obtenir une vue claire du code FSM.
le problème est, Je ne peux pas trouver d'exemples de ce genre de code dans Obj-C, et je ne suis pas si confiant sur la façon dont je l'ai traduit à partir du code d'exemple c++ que j'utilisais. (Je ne sais pas C++ à tous, il y a donc de deviner impliqués.) Je fonde cette version d'un dessin d'état sur cet article: http://www.ai-junkie.com/architecture/state_driven/tut_state1.html . Je ne fais pas un jeu, mais cet article décrit 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, y compris les protocoles obj-C, et ainsi de suite. Parce que ce sont de nouvelles pour moi, comme est le modèle de conception d'état, j'espère un certain retour sur cette mise en œuvre. Est-ce ainsi que vous travaillez efficacement avec les objets du protocole obj-C?
voici le protocole:
@class Timer;
@protocol TimerState
-(void) enterTimerState:(Timer*)timer;
-(void) executeTimerState:(Timer*)timer;
-(void) exitTimerState:(Timer*)timer;
@end
Voici l'objet Timer (dans sa forme la plus dépouillée) fichier d'en-tête:
@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'Objet de Minuteur de mise en œuvre:
#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. Il ne veut pas faire quelque chose d'intéressant encore. Actuellement, je suis juste mal à l'obtention de la syntaxe correcte. Actuellement, il compile (et fonctionne) mais le les appels de méthode isKindOfClass causent des avertissements de compilateurs (la méthode ne se trouve pas dans le protocole). Je ne suis pas sûr de vouloir utiliser isKindOfClass de toute façon. Je pensais donner à chaque objet id<TimerState>
une chaîne de noms et l'utiliser à la place.
Sur une autre note: tous ces id<TimerState>
déclarations ont été à l'origine TimerState * déclarations. Il semblait logique de les conserver en tant que propriétés. Je ne sais pas si ça a un sens avec id<TimerState>
.
ici est un exemple de l'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 sauf annoncer quelle phase (ou sous-état) il est. Mais ce n'est pas le point.
ce que j'espère apprendre ici, c'est si cette architecture est correctement composée dans le langage obj-C. Un problème spécifique que je rencontre est la création des objets id dans la fonction init de la minuterie. Comme vous pouvez le voir, j'ai commenté les versions, parce qu'ils causaient un avertissement de" rejet non trouvé dans le protocole". Je n'étais pas sûr de la façon de gérer cela.
ce dont je n'ai pas besoin, ce sont des commentaires sur le fait que ce code est exagéré ou un formalisme insignifiant, ou n'importe quoi d'autre. Ça vaut la peine que j'apprenne ça, même si ces idées sont vraies. Si cela peut aider, pensez-y comme une conception théorique pour une FSM dans obj-C.
Merci d'avance pour tout commentaire utile.
(cela n'a pas aidé trop: fini Machine d'État dans L'objectif c )
5 réponses
lorsque vous utilisez un protocole comme modificateur de type, vous pouvez fournir une liste de protocoles séparés par des virgules. Donc tout ce que vous devez faire pour vous débarrasser de l'avertissement du compilateur est d'ajouter NSObject à la liste des protocoles comme ceci:
- (void)setupTimer:(id<TimerState,NSObject>) timerState {
// Create scheduled timers, etc...
}
je suggère d'utiliser État Machine compilateur , il affichera objectif-c code. J'ai eu beaucoup de succès en Java et en Python en utilisant ceci.
vous ne devriez pas écrire le code machine d'état à la main, vous devriez utiliser quelque chose pour générer le code pour vous. SMC va générer nettoyer un code clair, vous pouvez ensuite regarder si vous voulez apprendre, ou vous pouvez simplement l'utiliser et être fait avec elle.
si vous voulez une implémentation Objective très simple d'une machine D'état, je viens de publier TransitionKit , qui fournit une API bien conçue pour implémenter des machines d'état. Il est soigneusement testé, bien documenté, très facile à utiliser, et ne nécessite pas de génération de code ou d'outils externes.
Je suis un peu nouveau à L'Objectif-C, mais je suggère que vous regardez directement l'implémentation ANSI C pour la Machine D'État.
ce n'est pas parce que vous utilisez du cacao que vous devez utiliser des messages objectifs-C.
dans ANSI C, une implémentation de machine d'état peut être très simple et lisible.
ma dernière mise en œuvre en C d'un FSM spécifié #define STATE_x
ou énumérer les types pour les États et avait un tableau de pointeurs de fonctions pour exécuter chaque état.