Cómo hacer una Máquina Básica de Estados Finitos en Objective-C


Estoy intentando construir un FSM para controlar un temporizador en (iphone sdk) objective c. Sentí que era un paso necesario, porque de lo contrario terminaría con un código espagueti desagradable que contiene páginas de declaraciones if-then. La complejidad, la no legibilidad y la dificultad de agregar/cambiar características me llevan a intentar una solución más formal como esta.

En el contexto de la aplicación, el estado del temporizador determina algunas interacciones complejas con NSManagedObjects, Core Data, y así adelante. He dejado toda esa funcionalidad fuera por ahora, en un intento de obtener una visión 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. Debido a que estos son nuevos para mí, al igual que el patrón de diseño del estado, espero algunos comentarios sobre esta implementación. ¿Es así como se trabaja con objetos de protocolo de manera efectiva en obj-c?

Aquí está el protocolo:

@class Timer;
@protocol TimerState 

-(void) enterTimerState:(Timer*)timer;
-(void) executeTimerState:(Timer*)timer;
-(void) exitTimerState:(Timer*)timer;

@end

Aquí está el objeto Timer (en su mayoría archivo de encabezado:

@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 implementación del Objeto 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

No te preocupes que faltan cosas en esta clase. No hace nada interesante todavía. Actualmente estoy luchando con conseguir la sintaxis correcta. Actualmente compila (y funciona) pero el método isKindOfClass llama a causar advertencias del compilador (el método no se encuentra en el protocolo). No estoy muy seguro de que quiera usar isKindOfClass de todos modos. Estaba pensando en dar cada id<TimerState> objeto una cadena de nombre y usar eso en su lugar.

En otra nota: todas esas declaraciones id<TimerState> eran originalmente declaraciones TimerState*. Parecía tener sentido retenerlos como propiedades. No estoy seguro de si tiene sentido con id<TimerState> ' s.

Aquí hay 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

De nuevo, hasta ahora no hace nada excepto anunciar en qué fase (o sub-estado) está. Pero ese no es el punto.

Lo que espero aprender aquí es si esto la arquitectura está compuesta correctamente en el lenguaje obj-c. Un problema específico que estoy encontrando es la creación de los objetos id en la función init del temporizador. Como puede ver, comenté las versiones, porque estaban causando una advertencia de" versión no encontrada en el protocolo". No estaba seguro de cómo manejar eso.

Lo que no necesito son comentarios acerca de que este código es exagerado o formalismo sin sentido, o lo que sea. Vale la pena que aprenda esto incluso si esas ideas son ciertas. Si ayuda, piensa de ti como un diseño teórico para un FSM en obj-c.

Gracias de antemano por cualquier comentario útil.

(esto no ayudó demasiado: Máquina de Estados Finitos en Objective-C )

Author: Community, 2010-03-10

5 answers

Cuando usa un protocolo como modificador de tipo, puede proporcionar una lista de protocolos separada por comas. Así que todo lo que necesita hacer para deshacerse de la advertencia del compilador es agregar NSObject a la lista de protocolos de la siguiente manera:

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

    // Create scheduled timers, etc...
}
 8
Author: jlehr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-09 22:42:33

Sugiero usar Compilador de Máquina de estados, generará código Objective-C. He tenido buen éxito en Java y Python usando esto.

No deberías estar escribiendo código máquina de estado a mano, deberías estar usando algo para generar el código por ti. SMC generará código limpio y claro que puede mirar si desea aprender de él, o simplemente puede usarlo y terminar con él.

 15
Author: feeling abused and harassed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-28 19:57:28

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

 7
Author: Blake Watters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-18 23:02:22

Sugeriría revisar Statec tiene un pequeño dsl agradable para hacer FSM y genera código ObjC. Es una especie de mogenerador para máquinas de estado.

 3
Author: Kamek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-11 22:28:54

Soy bastante nuevo en Objective-C, pero sugeriría que mires la implementación directa de ANSI C para la Máquina de Estados.

Solo porque estés usando Cocoa no significa que tengas que usar mensajes Objective-C aquí.

En ANSI C, una implementación de máquina de estado puede ser muy sencilla y legible.

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

 1
Author: Warren P,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-12 06:28:48