2010-03-09 14 views
29

Ich versuche, ein FSM zu erstellen, um einen Timer in (iPhone SDK) Ziel zu steuern c. Ich fühlte, dass es ein notwendiger Schritt war, weil ich sonst mit fiesen Spaghetti-Code endete, der Seiten von Wenn-Dann-Aussagen enthielt. Die Komplexität, die Unlesbarkeit und die Schwierigkeit, Funktionen hinzuzufügen/zu ändern, führen mich dazu, eine formellere Lösung wie diese zu versuchen.So erstellen Sie einen grundlegenden endlichen Automaten in Objective-C

Im Kontext der Anwendung bestimmt der Status des Zeitgebers einige komplexe Interaktionen mit NSManagedObjects, Core Data und so weiter. Ich habe diese Funktionalität für den Moment weggelassen, um den FSM-Code klar zu sehen.

Das Problem ist, ich kann keine Beispiele für diese Art von Code in Obj-C finden, und ich bin nicht so zuversichtlich, wie ich es aus dem C++ - Beispielcode, den ich verwendet habe, übersetzt habe. (Ich kenne C++ überhaupt nicht, also gibt es einige Raten.) Ich stütze diese Version eines Zustandsmusterdesigns auf diesem Artikel: http://www.ai-junkie.com/architecture/state_driven/tut_state1.html. Ich mache kein Spiel, aber dieser Artikel beschreibt Konzepte, die für das, was ich tue, funktionieren.

Um den Code (siehe unten) zu erstellen, musste ich eine Menge neuer Konzepte lernen, einschließlich obj-c-Protokolle und so weiter. Weil diese für mich neu sind, ebenso wie das Design des Staates, hoffe ich auf ein Feedback zu dieser Implementierung. Arbeiten Sie so effektiv mit Protokollobjekten in obj-c? Hier

ist das Protokoll:

@class Timer; 
@protocol TimerState 

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

@end 

Hier ist das Timer-Objekt (in seiner abgespeckten Form) Header-Datei:

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

Und die Timer Objekt Implementierung:

#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 

Keine Sorge, dass in dieser Klasse Dinge fehlen. Es macht noch nichts interessantes. Ich bin gerade dabei, die Syntax zu korrigieren. Derzeit kompiliert es (und funktioniert), aber die Aufrufe der isKindOfClass-Methode verursachen Compiler-Warnungen (Methode wurde im Protokoll nicht gefunden). Ich bin mir nicht wirklich sicher, ob ich isKindOfClass trotzdem benutzen möchte. Ich dachte daran, jedem id<TimerState> Objekt eine Namenszeichenfolge zu geben und diese stattdessen zu verwenden.

Auf eine andere Anmerkung: alle diese id<TimerState> Deklarationen waren ursprünglich TimerState * Deklarationen. Es schien sinnvoll, sie als Eigenschaften zu behalten. Nicht sicher, ob es mit id<TimerState>'s Sinn macht. Hier

ist ein Beispiel für eine der Zustandsklassen:

#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 

wieder so weit es nichts tut, außer bekannt, dass in welcher Phase (oder Unter Zustand) ist es in Aber das ist nicht die. Punkt.

Was ich hier zu lernen hoffe ist, ob diese Architektur korrekt in der Obj-c-Sprache zusammengesetzt ist. Ein spezifisches Problem, auf das ich stoße, ist die Erzeugung der ID-Objekte in der Init-Funktion des Timers. Wie Sie sehen können, habe ich die Releases auskommentiert, weil sie eine Warnung "Freigabe nicht im Protokoll gefunden" verursachten. Ich war mir nicht sicher, wie ich damit umgehen sollte.

Was ich nicht brauche, sind Kommentare über diesen Code, der übertrieben oder bedeutungsloser Formalismus ist, oder was auch immer. Es lohnt sich, das zu lernen, auch wenn diese Ideen wahr sind. Wenn es hilft, denke an es als theoretisches Design für eine FSM in obj-c.

Vielen Dank im Voraus für hilfreiche Kommentare.

(auch dies nicht hilft viel: Finite State Machine in Objective-C)

Antwort

8

Wenn Sie ein Protokoll als Typ-Modifikator verwenden, können Sie eine durch Kommata getrennte Liste von Protokollen zur Verfügung stellen. Also alles, was Sie tun müssen, um von der Compiler-Warnung loszuwerden, ist NSObject in die Protokollliste hinzufügen wie folgt:

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

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

Sie können auch ein Protokoll haben, das einem anderen Protokoll entspricht, so dass es nicht notwendig ist, jedes Mal zu erwähnen - erklären Sie es wie '@protocol TimerState '. Dies teilt dem Compiler mit, dass alle TimerState-Objekte auch dem NSObject-Protokoll entsprechen müssen. – Chuck

+0

Welches wäre fast sicher, was Sie in diesem Fall tun möchten. Ausgezeichneter Punkt. – jlehr

+0

Das macht den Compiler sicherlich glücklich. – mwt

1

Ich bin ziemlich neu in Objective-C, aber ich würde vorschlagen, dass Sie die Umsetzung gerade ANSI C für die State Machine zu sehen.

Nur weil Sie Cocoa verwenden bedeutet nicht, Sie hier Objective-C-Nachrichten verwenden.

In ANSI C kann eine Zustandsmaschine Umsetzung sehr einfach und lesbar sein.

Meine letzte Implementierung in C einer FSM angegeben #define STATE_x oder Aufzählungstypen für die Staaten und hatte eine Tabelle mit Zeigern auf Funktionen, um jeden Zustand auszuführen.

+1

Der ai-junkie.com-Artikel, der oben erwähnt wird, bespricht, warum das Design, das ich oben versuche, dem Tabelle-der-Funktionen Entwurf überlegen ist. Ich weiß nicht, dass es eigentlich * ein besserer Weg ist, aber seine Ideen sind lesenswert. – mwt

+0

Ich denke, ich sollte "angeblich besser" sagen. – mwt

15

Ich schlage vor, State Machine Compiler verwenden, gibt er Objective-C Code. Ich hatte einen guten Erfolg in Java und Python.

Sie nicht Zustand Maschinencode von Hand schreiben sollte, sollten Sie etwas verwenden, den Code für Sie zu generieren. SMC erzeugt sauberen, klaren Code, den Sie dann ansehen können, wenn Sie daraus lernen wollen, oder Sie können es einfach verwenden und damit fertig sein.

+1

Oh. Ich wusste nichts davon. –

+1

Das ist interessant, obwohl es meine Frage nicht wirklich anspricht. Diese – mwt

+1

ist die beste Antwort, die ich gesehen habe auf SO noch – Nektarios

3

Ich würde vorschlagen, Statec Check-out es hat eine nette kleine dsl für FSM tun und gibt ObjC Code . Es ist wie ein Generator für State Machines.

7

Wenn Sie eine sehr einfache, Objective-C-Implementierung einer State Machine möchten, habe ich gerade TransitionKit freigegeben, die eine gut gestaltete API für die Implementierung von State Machines bietet. Es ist gründlich getestet, gut dokumentiert, sehr einfach zu bedienen und erfordert keine Code-Generierung oder externe Tools.

Verwandte Themen