2010-03-09 13 views
29

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)

Respuesta

8

Cuando utiliza un protocolo como modificador de tipo, puede proporcionar una lista de protocolos separados por comas. Así que todo lo que necesita hacer para deshacerse de la advertencia del compilador es añadir NSObject a la lista de protocolos de esta manera:

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

    // Create scheduled timers, etc... 
} 
+15

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

+0

Cuál sería casi seguramente lo que le gustaría hacer en este caso. Excelente punto – jlehr

+0

Eso ciertamente hace feliz al compilador. – mwt

1

soy bastante nuevo en Objective-C, pero sugeriría que nos fijamos en la aplicación recta ANSI C para la máquina de estado.

El hecho de que esté utilizando Cocoa no significa que tenga que usar los mensajes de Objective-C aquí.

En ANSI C, la implementación de una máquina de estado puede ser muy sencilla y fácil de leer.

Mi última implementación en C de un FSM especificó #define STATE_x o enumere los tipos para los estados y tenía una tabla de punteros a las funciones para ejecutar cada estado.

+1

El artículo de ai-junkie.com mencionado anteriormente explica por qué el diseño que intento anteriormente es superior al diseño de la tabla de funciones. No sé si realmente * es * una mejor forma de hacerlo, pero vale la pena leer sus ideas. – mwt

+0

Supongo que debería decir "supuestamente mejor". – mwt

15

Sugiero usar State Machine Compiler, dará salida al código Objective-C. He tenido mucho éxito en Java y Python al usar esto.

No debe escribir código de máquina de estado a mano, debe utilizar algo para generar el código para usted. SMC generará un código limpio y claro que luego podrá ver si desea aprender de él o simplemente puede usarlo y terminarlo.

+1

Oh. No sabía sobre esto. –

+1

Eso es interesante, aunque realmente no aborda mi pregunta. – mwt

+1

Esta es la mejor respuesta que he visto en SO aún – Nektarios

3

yo sugiero revisar Statec que tiene un pequeño DSL para hacer FSM y salidas de código ObjC . Es una especie de mogenerador para máquinas de estado.

7

Si desea una implementación Objective-C muy sencilla de una máquina de estado, acabo de lanzar TransitionKit, que proporciona una API bien diseñada para implementar máquinas de estado. Se ha probado exhaustivamente, está bien documentado, es muy fácil de usar y no requiere generación de código ni herramientas externas.

Cuestiones relacionadas