您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
iOS APP 架构漫谈(二)
 
作者:zhenby's blog 发布于 2015-8-11
   次浏览      
 

上一篇《iOS APP 架构漫谈(一)》简单介绍了information flow的概念。这篇文章简单介绍另一个在编程中非常重要的思想或工具——状态机(State machine)。 对大多数计算机专业的家伙们来说,这应该是一门比较难学的课程,里面包含一大堆揪心的名字比如DFA,NFA,还有一大堆各种各样的数学符号,又是编译原理的基础。不过很遗憾,似乎在做完编译原理课程作业之后,很多人再也没有实现过或是用过状态机了。本文通过一个游戏demo来简单描述一下状态机在实践中的应用。

背景

首先看下我们的使用场景,假如我们需要设计一套联网对战的小游戏。第一个难题可能是如何建立一个通道,让2个手机相互发送消息。这里我并不打算引入server端开发,希望只是通过客户端来实现这个逻辑,这里使用LeanCloud API来简化这个过程。这样我们可以暂时不考虑技术细节,直接站在业务角度去思考如何建立这个游戏。

业务场景–邀请

正式开始游戏之前,总会有一个邀请的环节。假如我们有2个用户,分别是Host,Guest。Host创建游戏,Guest加入游戏。游戏的整个流程和我们平时玩的对战游戏流程并没有多大不同。

1. Host创建游戏,他就相当于进入一个等待队列里面。

2. Guest加入游戏,他从等待队列中找到一个匹配,比如Host。然后对Host发送join message

3. Host会收到很多join message。由于我们只是选择1vs1。这里假定Host同意Guest加入游戏。Host向Guest发送join confirm message

4. Guest收到join confirm message, 向Host发送Go消息,表示Guest已经进入游戏

5. Host收到Go消息。也进入游戏。

具体实现业务逻辑

现在的构想的逻辑只有5步,但其实还会包含很多逻辑,比如超时机制,重发机制。由于中间状态很多,还可能有我们没有想到过的问题。在面对这种复杂逻辑时,会通过状态机来帮助我们理顺逻辑。这时,我们脑中思考的业务其实是一个状态到一个状态的图。 如下

上半部分是游戏的创建者,下半部分是游戏的加入者。

一开始,尽量简化模型,这里红色剪头表示我们的正确主流路线,黑色表现出错路线。也就是说,一旦错误,就回到原始Idle状态。

开始写代码

在想清楚所有逻辑,并考虑清楚正常路线和错误路线之后,就可以开始写代码了。为了方便,这里直接使用第三方的状态机框架TransitionKit。

定义State(HOST)

TKState *idleState = [TKState stateWithName:@"idle"];
TKState *waitingJoinState = [TKState stateWithName:@"waitingJoin"];
TKState *waitingConfirmState = [TKState stateWithName:@"waitingConfirm"];
TKState *goState = [TKState stateWithName:@"go"];

[waitingConfirmState setDidEnterStateBlock:^(TKState *state, TKTransition *transition) {
[selfWeak sendJoinConfirm];
}];

[goState setDidEnterStateBlock:^(TKState *state, TKTransition *transition) {
NSLog(@"happy ending");

[SVProgressHUD showSuccessWithStatus:@"ok"];
}];

定义Event(HOST)

Event 是建立State到State的路径

TKEvent *waitingJoinEvent = [TKEvent eventWithName:CUHostGameManagerWaitingJoinEvent
transitioningFromStates:@[idleState]
toState:waitingJoinState];

TKEvent *receiveInviteEvent = [TKEvent eventWithName:CUHostGameManagerReceiveInviteEvent
transitioningFromStates:@[waitingJoinState]
toState:waitingConfirmState];

TKEvent *receiveConfirmEvent = [TKEvent eventWithName:CUHostGameManagerReceiveConfirmEvent
transitioningFromStates:@[waitingConfirmState]
toState:goState];

TKEvent *disconnectedEvent = [TKEvent eventWithName:CUHostGameManagerDisconnectedEvent
transitioningFromStates:nil
toState:idleState];

定义过程(HOST)

- (void)startGame {

NSAssert(self.session.peerId != nil, @"");

//这里,如果不是idle,我们切换状态机到idle
if (![self.stateMachine.currentState.name isEqual:@"idle"]) {
[self fireEvent:CUHostGameManagerDisconnectedEvent userInfo:nil];
}

//这里调用LeanCloud 入队
AVObject *waitingId = [AVObject objectWithClassName:@"waiting_join_Ids"];
[waitingId setObject:self.session.peerId forKey:@"peerId"];
[waitingId saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
//enqueue 之后,进入waitingJoin状态
[self fireEvent:CUHostGameManagerWaitingJoinEvent userInfo:nil];
}];
}

- (void)sendJoinConfirm {
//发送加入确认消息给Guest
AVMessage *message = [AVMessage messageForPeerWithSession:self.session
toPeerId:self.peerId
payload:@"join_confirm"];
[self.session sendMessage:message transient:YES];
}

- (void)session:(AVSession *)session didReceiveMessage:(AVMessage *)message
{
if ([message.payload isEqualToString:@"join"]) {
//收到Join(邀请)之后,发送确认消息
self.peerId = message.fromPeerId;

//因为LeanCloud的API比较挫,watch 之后才能发送消息,但是我们不知道什么时候才watch成功。。。。
//好在只是demo,我们只好用这种方式work around,延迟2s发送消息
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(sendInviteConfirmRequest:) object:nil];
[self performSelector:@selector(sendInviteConfirmRequest:)
withObject:@[message.fromPeerId]
afterDelay:2.0f];
}
else if ([message.payload isEqualToString:@"go"]) {
//收到go消息,流程结束
[self fireEvent:CUHostGameManagerReceiveConfirmEvent userInfo:nil];
}
}

- (void)sendInviteConfirmRequest:(NSArray *)watchPeerIds {
[self.session watchPeerIds:watchPeerIds];
[self fireEvent:CUHostGameManagerReceiveInviteEvent userInfo:nil];
}

定义State(Guest)

TKState *idleState = [TKState stateWithName:@"idle"];
TKState *waitingReplyState = [TKState stateWithName:@"waitingReply"];
TKState *goState = [TKState stateWithName:@"go"];

[waitingReplyState setWillEnterStateBlock:^(TKState *state, TKTransition *transition) {
[selfWeak searchingGames];
}];

[goState setDidEnterStateBlock:^(TKState *state, TKTransition *transition) {
[selfWeak sendGo];
NSLog(@"happy ending");
[SVProgressHUD showSuccessWithStatus:@"ok"];
}];

定义Event(Guest)

TKEvent *searchingEvent = [TKEvent eventWithName:CUGestGameManagerSearchingEvent
transitioningFromStates:@[idleState]
toState:waitingReplyState];

TKEvent *receiveConfirmEvent = [TKEvent eventWithName:CUGestGameManagerReceiveConfirmEvent
transitioningFromStates:@[waitingReplyState]
toState:goState];

TKEvent *disconnectedEvent = [TKEvent eventWithName:CUGestGameManagerDisconnectedEvent
transitioningFromStates:nil
toState:idleState];

定义过程(Guest)

- (void)joinGame {

if (![self.stateMachine.currentState.name isEqual:@"idle"]) {
[self fireEvent:CUGestGameManagerDisconnectedEvent userInfo:nil];
}

[self fireEvent:CUGestGameManagerSearchingEvent userInfo:nil];
}

- (void)searchingGames {
AVQuery *query = [AVQuery queryWithClassName:@"waiting_join_Ids"];
[query orderByDescending:@"updatedAt"];
[query setLimit:1];

[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSMutableArray *installationIds = [[NSMutableArray alloc] init];
for (AVObject *object in objects) {
if ([object objectForKey:@"peerId"]) {
[installationIds addObject:[object objectForKey:@"peerId"]];
}
}

[self.session watchPeerIds:installationIds];

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(sendJoinRequest) object:nil];
[self performSelector:@selector(sendJoinRequest)
withObject:nil
afterDelay:2.0f];
}];
}

- (void)sendJoinRequest {

for (NSString *item in self.session.watchedPeerIds) {
AVMessage *message = [AVMessage messageForPeerWithSession:self.session
toPeerId:item
payload:@"join"];
[self.session sendMessage:message transient:YES];
}
}

- (void)sendGo{
AVMessage *message = [AVMessage messageForPeerWithSession:self.session
toPeerId:self.otherPeerId
payload:@"go"];
[self.session sendMessage:message transient:YES];
}

最后

state machine 是一个蛮厉害的锤子,只要是一个工具,就肯定会被滥用。。。state machine最大的好处是在于,方便我们思考清楚所有细节,主线,和错误流程。避免因为考虑不周全而产生的bug。结合之前的information flow的思路,会让我们的软件设计更加清楚。

   
次浏览       
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   

Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合

领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...