如何在Objective-C中制作基本的有限状态机

31

我试图构建一个有限状态机来控制(iPhone SDK)Objective-C中的定时器。我认为这是必要的步骤,因为否则我会得到一堆充满if-then语句的混乱代码。复杂性、难以阅读和添加/更改功能的困难性促使我尝试像这样更正式的解决方案。

在应用程序的上下文中,计时器的状态确定了与NSManagedObjects、Core Data等的一些复杂交互。我现在把所有这些功能都留了下来,以便清楚地查看FSM代码。

问题在于,我找不到任何Obj-C中的这种代码示例,而且我对我从正在使用的C++示例代码翻译的正确性也不太自信。 (我根本不懂C++,所以需要一些猜测。)我基于这篇文章设计了一种状态模式,http://www.ai-junkie.com/architecture/state_driven/tut_state1.html。我没有做一个游戏,但这篇文章概述了我的工作所需的概念。

为了创建下面发布的代码,我不得不学习很多新概念,包括Obj-C协议等。由于这些都是对我来说比较新的概念,状态设计模式也是如此,我希望得到一些关于这个实现的反馈。这是否是你在Obj-C中有效使用协议对象的方式?

以下是协议:

@class Timer;
@protocol TimerState 

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

@end

这是计时器对象(最简化的形式)的头文件:

@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;

计时器对象实现:

#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

不要担心这个类中缺少的东西,它目前还没有做任何有趣的事情。我目前只是在努力让语法正确。目前它能编译(并工作),但isKindOfClass方法调用会导致编译器警告(协议中找不到该方法)。我真的不确定是否要使用isKindOfClass。我想为每个id<TimerState>对象赋予一个名称字符串,并使用该名称字符串代替。

另外一点:所有那些id<TimerState>声明最初都是TimerState *声明。将它们保留为属性似乎很有意义。不确定对于id<TimerState>是否合适。

这是状态类之一的示例:
#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

目前为止,它除了宣布它处于哪个阶段(或子状态)之外,什么也没有做。但这不是重点。

我希望在这里学到的是这种架构是否在obj-c语言中正确组成。我遇到的一个具体问题是在定时器的init函数中创建id对象。如您所见,我注释掉了releases,因为它们导致“在协议中找不到释放”的警告。我不确定如何处理这个问题。

我不需要关于这段代码过度复杂或无意义形式主义的评论,或者其他任何东西。即使这些想法是真实的,我还是值得学习这个。如果有帮助,请把它看作是一种在obj-c中进行FSM的理论设计。

非常感谢您提供任何有用的评论。

(这篇文章对我没有太大帮助:Objective-C中的有限状态机

5个回答

15

我建议使用状态机编译器,它会输出Objective-C代码。在Java和Python中,我使用这个工具取得了很好的成功。

你不应该手写状态机代码,而是应该使用一些工具来为你生成代码。SMC将生成干净清晰的代码,如果你想学习,可以查看它,或者你也可以直接使用它并完成任务。


1
这很有趣,虽然它并没有真正回答我的问题。 - mwt
1
这是我在SO上看到的最好的答案。 - Nektarios
我遇到的一个问题是,这个编译器不允许在动作方法(SMC符号中的“action”)内部调用fsm的转换方法。 - peetonn
@mwt它说“如何制作基本有限机器...”,我的回答重点是语言并不重要,这种类型的代码应该在某个DSL中声明,并通过自动生成而不是手工构建和维护。 - user177800

8
当你将协议作为类型修饰符使用时,可以提供一个逗号分隔的协议列表。所以,为了消除编译器警告,你只需要像这样将NSObject添加到协议列表中:
- (void)setupTimer:(id<TimerState,NSObject>) timerState {

    // Create scheduled timers, etc...
}

15
你还可以将一个协议符合另一个协议,这样每次使用时就不需要同时声明两个了 - 声明方式是 @protocol TimerState <NSObject>。这告诉编译器所有符合 TimerState 协议的对象也必须符合 NSObject 协议。 - Chuck
在这种情况下,这几乎肯定是您想要做的。非常好的观点。 - jlehr
那确实让编译器高兴。 - mwt

8
如果你想要一个非常简单的、Objective-C 实现的状态机,我刚刚发布了 TransitionKit,它提供了一个设计良好的 API 来实现状态机。它经过了全面的测试、有着完善的文档、非常容易使用,而且不需要任何代码生成或外部工具。请查看 TransitionKit

3
我建议查看Statec,它有一个不错的DSL用于执行FSM并输出ObjC代码。它有点像状态机的mogenerator。 Statec

1

我对Objective-C还比较新,但我建议您查看直接使用ANSI C实现状态机。

仅仅因为您正在使用Cocoa并不意味着您必须在此处使用Objective-C消息。

在ANSI C中,状态机实现可以非常简单和易读。

我上次在C中实现的FSM指定了#define STATE_x或枚举类型的状态,并具有指向执行每个状态的函数的指针表。


1
上面提到的ai-junkie.com文章讨论了为什么我尝试的设计比函数表格设计更优越。我不知道它是否真的是更好的选择,但他的想法值得一读。 - mwt
我想我应该说“据说更好”。 - mwt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接