Estoy intentando construir un FSM para controlar un temporizador en (iphone sdk) objetivo c. Sentí que era un paso necesario, porque de lo contrario terminaba con un desagradable código de spaghetti que contenía páginas de declaraciones de if-then. La complejidad, la falta de legibilidad y la dificultad de agregar/cambiar características me llevan a intentar una solución más formal como esta.Cómo hacer una máquina básica de estados finitos en Objective-C
En el contexto de la aplicación, el estado del temporizador determina algunas interacciones complejas con NSManagedObjects, Core Data, y así sucesivamente. Por ahora, he dejado fuera toda esa funcionalidad, en un intento de obtener una vista clara del código FSM.
El problema es que no puedo encontrar ningún ejemplo de este tipo de código en Obj-C, y no estoy tan seguro de cómo lo he traducido del código de ejemplo de C++ que estaba usando. (No conozco C++ en absoluto, así que hay algunas conjeturas involucradas.) Estoy basando esta versión de un diseño de patrón de estado en este artículo: http://www.ai-junkie.com/architecture/state_driven/tut_state1.html. No estoy haciendo un juego, pero este artículo describe conceptos que funcionan para lo que estoy haciendo.
Para crear el código (publicado a continuación), tuve que aprender muchos conceptos nuevos, incluidos los protocolos obj-c, y así sucesivamente. Como estos son nuevos para mí, al igual que el patrón de diseño del estado, espero recibir algunos comentarios sobre esta implementación. ¿Es así como trabajas con objetos de protocolo de forma efectiva en obj-c?
Aquí es el protocolo:
@class Timer;
@protocol TimerState
-(void) enterTimerState:(Timer*)timer;
-(void) executeTimerState:(Timer*)timer;
-(void) exitTimerState:(Timer*)timer;
@end
Aquí es el objeto Timer (en su forma más simplificada) archivo de cabecera:
@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;
Y la ejecución del temporizador del objeto:
#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
No se preocupe si le faltan cosas en esta clase. No hace nada interesante todavía. Actualmente estoy luchando para obtener la sintaxis correcta. Actualmente compila (y funciona) pero el método isKindOfClass llama a las advertencias del compilador (el método no se encuentra en el protocolo). No estoy seguro de querer usar isKindOfClass de todos modos. Estaba pensando en darle a cada objeto id<TimerState>
una cadena de nombre y usar eso en su lugar.
En otra nota: todas esas declaraciones id<TimerState>
fueron originalmente declaraciones de TimerState *. Parecía tener sentido retenerlos como propiedades. No estoy seguro si tiene sentido con id<TimerState>
.
Aquí es un ejemplo de una de las clases de estado:
#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
Una vez más, hasta ahora no hace nada más que anuncian que lo fase (o sub-estado) que se encuentra en Pero ese no es el. punto.
Lo que espero aprender aquí es si esta arquitectura está compuesta correctamente en el lenguaje obj-c. Un problema específico que estoy encontrando es la creación de los objetos de identificación en la función de inicio del temporizador. Como puede ver, comenté los lanzamientos, porque estaban causando una advertencia de "liberación no encontrada en el protocolo". No estaba seguro de cómo manejar eso.
Lo que no necesito son comentarios sobre este código que es un formalismo exagerado o sin sentido, o lo que sea. Merece la pena aprender esto incluso si esas ideas son verdaderas. Si ayuda, piense en ello como un diseño teórico para un FSM en obj-c.
Gracias de antemano por cualquier comentario útil.
(esto no ayudó demasiado: Finite State Machine in Objective-C)
También puede hacer que un protocolo se ajuste a otro protocolo para que no sea necesario mencionar ambos cada vez; asígnelo como '@protocol TimerState'. Esto le dice al compilador que todos los objetos TimerState también deben cumplir con el protocolo NSObject. –
Chuck
Cuál sería casi seguramente lo que le gustaría hacer en este caso. Excelente punto – jlehr
Eso ciertamente hace feliz al compilador. – mwt