TBStateMachine 6.11.0

TBStateMachine 6.11.0

测试已测试
Lang语言 Obj-CObjective C
许可证 MIT
发布最后发布2023年6月

Julian KrumowJulian Krumow 维护。



  • 作者
  • Julian Krumow

TBStateMachine

Version License Platform Build Status

一个轻量级的 Objective-C 级联状态机框架。

特性

  • 基于块的 API
  • 嵌套状态
  • 正交区域
  • 虚拟状态(分支、汇合和交叉点)
  • 具有守卫和动作的外部、内部和本地转移
  • 使用最小编码等级算法(LCA)进行状态切换
  • 线程安全的事件处理
  • 异步事件处理
  • 支持NSNotificationCenter

Features

要求

  • watchOS 2.0
  • iOS 6.0
  • OS X 10.8

TBStateMachine 可以通过 CocoaPods 获得。要安装,只需将以下行添加到您的 Podfile 中:

pod 'TBStateMachine'

#import <TBStateMachine/TBSMStateMachine.h>

创建状态,设置进入和退出代码块

TBSMState *a = [TBSMState stateWithName:@"a"];
a.enterBlock = ^(id data) {

};
    
a.exitBlock = ^(id data) {

};

创建状态机

TBSMStateMachine *stateMachine = [TBSMStateMachine stateMachineWithName:@"main"];

添加状态并设置状态机。除非您显式设置初始状态,否则状态机始终将数组中给出的第一个状态作为初始状态

stateMachine.states = @[a, b, ...];
stateMachine.initialState = a;
[stateMachine setUp:nil];

您可以添加事件处理器,触发到特定目标状态的转变

[a addHandlerForEvent:@"transition_1" target:b];

您还可以添加具有附加动作和守卫块的事件处理器

TBSMActionBlock action = ^(id data) {

};

TBSMGuardBlock guard = ^BOOL(id data) {

    return YES;
};

[a addHandlerForEvent:@"transition_1" target:b kind:TBSMTransitionExternal action:action guard:guard];

如果您为同一事件注册了多个处理器,则守卫块将决定哪个转变将被触发。

不同类型的转换

默认情况下,转换是外部的。要显式定义转换类型,请选择以下三个类型属性之一

TBSMTransitionExternal
TBSMTransitionInternal
TBSMTransitionLocal

调度事件

要调度事件,请调用 scheduleEvent: 并传递指定的 TBSMEvent 实例以及(可选的)负载对象

TBSMEvent *event = [TBSMEvent eventWithName:@"transition_1" data:aPayloadObject];
[stateMachine scheduleEvent:event];

负载将在所有执行直到事件成功处理为止的动作、守卫、进入和退出块中可用。

枚举事件

如果您不想像这样为每个事件编写字符串常量

FOUNDATION_EXPORT NSString * const Transition_1;

NSString * const Transition_1 = @"transition_1";

可以使用一个结构体

FOUNDATION_EXPORT const struct StateMachineEvents {
    __unsafe_unretained NSString *Transition_1;
    __unsafe_unretained NSString *Transition_2;
} StateMachineEvents;

const struct StateMachineEvents StateMachineEvents = {
    .Transition_1 = @"transition_1",
    .Transition_2 = @"transition_2"
};

可以这样使用它

[stateMachine scheduleEventNamed:StateMachineEvents.Transition_1 data:aPayloadObject];

运行到完成

事件处理遵循运行到完成模型,以确保一次只处理一个事件。单个RTC步骤封装了从评估事件到执行转换直到执行守卫、动作、退出和进入块的整个过程。

事件将按顺序排队并处理。

嵌套状态

TBSMState 实例可以通过 TBSMSubState 进行嵌套。

TBSMSubState *b2 = [TBSMSubState subStateWithName:@"b2"];
b2.states = @[b21, b22];

由于 TBSMSubStateTBSMState 的子类型,您还可以在该类型上注册事件、添加进入和退出块。

正交区域

要构建正交区域,请使用 TBSMParallelState

TBSMParallelState *b3 = [TBSMParallelState parallelStateWithName:@"b3"];
b3.states = @[@[b311, b312], @[b321, b322]];

伪状态

TBStateMachine 支持使用分叉和汇合伪状态来构建复合转换。

分叉

TBSMFork *fork = [TBSMFork forkWithName:@"fork"];
[a addHandlerForEvent:@"transition_15" target:fork];
[fork setTargetStates:@[c212, c222] inRegion:c2];

汇合

TBSMJoin *join = [TBSMJoin joinWithName:@"join"];
[c212 addHandlerForEvent:@"transition_16" target:join];
[c222 addHandlerForEvent:@"transition_17" target:join];
[join setSourceStates:@[c212, c222] inRegion:c2 target:b];

节点

TBSMJunction *junction = [TBSMJunction junctionWithName:@"junction"];
[a addHandlerForEvent:@"transition_18" target:junction];
[junction addOutgoingPathWithTarget:b1 action:nil guard:^BOOL(id data) {
    return (data[@"goB1"]);
}];
[junction addOutgoingPathWithTarget:c2 action:nil guard:^BOOL(id data) {
    return (data[@"goC2"]);
}];

通知

TBSMState 在进入和退出时发布 NSNotification

  • TBSMStateDidEnterNotification
  • TBSMStateDidExitNotification

通知的 userInfo 包含

{
    TBSMDataUserInfo:theData
}

要接收通知

[self.stateMachine subscribeToEntryAtPath:@"c/c2@1/c222" forObserver:self selector:@selector(myHandler:)];
[self.stateMachine subscribeToExitAtPath:@"c/c2@1/c222" forObserver:self selector:@selector(myHandler:)];

- (void)myHandler:(NSNotification *)notification
{
    id myPayloadObject = notification.userInfo[TBSMDataUserInfo];
}

TBSMState 还在执行内部转换时发布带有事件名称的 NSNotification

[self.stateMachine subscribeToAction:@"transition_10" atPath:@"a/a1" forObserver:self selector:@selector(myHandler:)];

要通过路径方案(如上所示)查找特定状态,可以使用该方案。路径由用斜杠分隔的状态名称组成

b/b2/b21

在正交区域内,添加了 @ 符号和索引以选择特定的子区域

c/c2@1/c222

配置文件

状态机可以通过 json 文件进行配置并由 TBSMStateMachineBuilder 构建

pod 'TBStateMachine/Builder'
#import <TBStateMachine/TBSMStateMachineBuilder.h>

NSString *path = // path to file
TBMSStateMachine *stateMachine = [TBSMStateMachineBuilder buildFromFile:path];

配置文件的格式可以在测试固定文件中查看。

配置文件的 json 格式遵循Pod/Builder/Schema/schema.json中定义的 json-schema。

有关 json schema 的一般信息请参阅https://json-schema.fullstack.org.cn

线程安全与并发

TBStateMachine 是线程安全的。默认情况下,每个事件都在主队列上异步处理。这使得处理 UIKit 组件变得方便。

要使用专用的后台队列,只需设置

NSOperationQueue *queue = [NSOperationQueue new];
queue.name = @"com.myproject.queue";
queue.maxConcurrentOperationCount = 1;
stateMachine.scheduledEventsQueue = queue;

调试支持

TBStateMachine 通过子规范 DebugSupport 提供调试支持。只需将其添加到您的 Podfile 中(很可能添加到一个 beta 目标,以避免在生产代码中使用)

target 'MyBetaApp', :exclusive => true do
  pod 'TBStateMachine/DebugSupport'
end

然后،在堆栈顶部包含 TBSMDebugger.h 以调试状态机

#import <TBStateMachine/TBSMDebugger.h>

[[TBSMDebugger sharedInstance] debugStateMachine:stateMachine];

状态机将输出每个事件、转换、初始化、清理、进入和退出等的日志消息,包括执行 Run-to-Completion 步骤的持续时间

[Main]: attempt to handle event 'transition_4' data: 12345
[stateA] will handle event 'transition_4' data: 12345
[Main] performing transition: stateA --> stateCc data: 12345
    Exit 'a3' data: 12345
    Exit 'a' data: 12345
    Enter 'b' data: 12345
    Enter 'b2' data: 12345
    Enter 'b21' data: 12345
[Main]: run-to-completion step took 1.15 milliseconds
[Main]: remaining events in queue: 1
[Main]: (
    transition_8
)

在调试实例上调用 -activeStateConfiguration 时,您将获得整个堆栈的当前活动状态配置

NSLog(@"%@", [[TBSMDebugger sharedInstance] activeStateConfiguration]);
Main
    b
        bSubMachine
            b2
            	b2Submachine
            		b21

开发设置

首先克隆仓库,然后从 Example 目录运行 pod install。项目包含针对开发的单元测试目标。

关于 UML 状态机的有用理论

作者

Julian Krumow, [邮箱保护: obfuscatron.com]

许可协议

TBStateMachine 在MIT许可下可用。更多信息请参阅LICENSE文件。