由于移动平台的特性,其网络状态不可靠,对于依赖于网络交互工作的软件,面临的最大的问题就是网络的有效性,包括连不上,网络状况差,网络切换等
于是,很自然的一个问题就是当app发送网络请求(无论是用户还是软件本身)的时候,会出现如下一些问题:
- 能否成功发送出去
- 发送出去后能否接收的到
- 回应包重复
- 回应包乱序
- 错误包
针对第一个问题,可以使用命令模式(Command Pattern),将所有的网络请求封装成命令。iOS提供了NSInvocation来实现命令模式,该类有个局限性,就是一个NSInvocation只能触发一个实际的action,但这个问题不影响我们的使用。
举一个简单的例子,假设有登录,获取个人信息,登出三个请求,当客户端Client调用的时候,实际将生成3个命令,类似
,三个均继承自NSInvocation,这里使用继承而不是直接定制NSInvocation是因为后面需要扩展NSInvocation的功能
@interface LoginCommand:NSInvocation
@end
@interface QueryInfoCommand:NSInvocation
@end
@interface LogoutCommand:NSInvocation
@end
,三个均继承自NSInvocation,这里使用继承而不是直接定制NSInvocation是因为后面需要扩展NSInvocation的功能
Client在“发出”一个命令后就不再拥有这个对象,但由于这个命令可能在网络不可用的时候需要等待一段时间,所以我们需要一个Poster来保留待发的命令,它负责Retain所有的Command,当网络可用时,执行(发送)它们
@interface CommandPoster:NSObject{
NSMutableArray *_commands;
}
@end
谁负责接收回应包呢?这里不是指传输层面的,而是实际接收传输层发送过来的回应包,在后面的章节中会设计回应包的接收模式为观察者模式(Observer Pattern),所以这里应该考虑是谁负责注册这个观察者,考虑到Client在发出命令后,其生命期不一定一直存在,由命令对象本身注册观察者应该是个合理的策略。既然各个命令对象是运行时确定的,本身当然知道该观察哪一种回应包
命令收到回应还没有结束,Client在发出命令后也需要及时得到回应,比如等待对话框需要在这个时候关闭,为得到这个回应,有多种选择,比如每个命令提供一个协议接口,Client实现该协议,在命令收到回应的时候通知协议接口,或者使用观察者模式,由于Poster在某些情况下面也需要得到这个通知,在这种1对多的情况下面,使用通知比较合理
以上是一个典型的命令-响应包的过程,但还有些特殊的过程需要考虑,就是一个命令可能没有回应包(或者客户端忽略该回应包),或者有多个回应包,这些情况主要会影响命令的生命期
Client可以手动Cancel命令(比如超时等),Cancel掉后,命令将自己从Poster中删除,或者命令在收到回应通知了协议后,删除自己
这里有一个问题,如果Client在回应包到达前销毁掉了,该回应将得不到处理,有几种选择:
- 干脆在Client销毁前,将命令也销毁
- 在命令收到回应后,自己负责处理
选择前面一种方式意味着Client需要保留每一个发出的命令的引用
很明显,如果未登录或者登录失败(包括超时或者用户名密码错误等),获取个人信息是没有意义的,也就是说当得到登录回应包并且回应包表示登录成功后,第二个包,获取个人信息才能发送,在这个例子中,我们可以在用户界面层阻止用户在登录过程中做任何的操作,这样做一般也是合理的,但假设一种情况,当网络异常,发生与服务器中断的时候,我们想让客户端在网络恢复后自动在后台登录,在网络异常到后台登录这段时间内,禁止用户的任何网络请求是不太合适的,也就是说,我们需要在Poster中控制命令的发送策略,比如在发送登录命令到接收到回应的这段时间,Poster会暂停发送后面的命令包,这里就需要监听该命令包的接收状态(前面命令采用通知的方式的原因)
以上是正常情况下面,命令从生成到销毁的过程,简单概括一下,某个客户端或者模块根据需要生成特定的命令对象,将对象交给Poster,并监视该对象的状态,Poster在合适的时候执行命令,当命令得到响应包后,通知大家,客户端处理完该响应,命令删除自己
上面的设计没有考虑网络状态的影响,实际上这里是故意不考虑网络的,我的想法是将网络状态作为Poster的内部状态,即Poster负责管理和维护网络状态,外部的模块就可以忽略网络变化带来的复杂性