我遇到的问题
我在做项目开发的时候,遇到了这样两个页面:
- A页面是一个关注列表页面,有列表接口与关注与取消关注接口
- B页面是一个他人主页页面,有他人信息接口与关注与取消关注接口
可以看到,这两个页面都会用到关注与取消关注接口:

一般1个ViewController会对应1个ViewModel,而ViewModel可能会对应多个网络请求:

如果我们项目简单,直接多个网络请求放在一个ViewModel中,没有问题。
但是随着项目的复杂度增加,我们可能会遇到这样的问题:ViewModel中有多个网络请求,而其中某个或者某个网络请求可能会在多个ViewModel中复用。
使用继承可以吗?
有人可能会思考,那么就将业务复用接口写到一个基类ViewModel中继承即可,比如下面这样的伪代码:
swift
class 他人主页ViewModel: 关注列表ViewModel {
/// 新写一个他人信息接口
}
那么此时,他人主页ViewModel实际上包含了3个接口:
列表接口 (我们不需要)- 关注与取消接口
- 他人信息接口
显然,这样业务耦合了,并且他人主页中出现了我们不需要使用的列表接口!
通过类的继承,明显无法满足每一个接口都独立并且解耦,因为Swift是单继承关系。
换个角度
既然class的类继承不行,我们可以试试使用protocol。
protocol可以多继承,而且protocol可以被多个类遵循,这样的话,我只需要将每个一个接口都通过protocol来定义与实现。
于是我先定义了一个空协议BaseRequestable,然后每个网络请求都定义一个协议,并且继承BaseRequestable协议,并在extension中进行实现:
swift
/// 基类协议,让其他业务请求都继承这个协议,如此一来,万一需要as?进行转换留余地
protocol BaseRequestable {}
/// 列表接口
protocol ListRequest: BaseRequestable {
func requestList() -> Single<Moya.Response>
}
extension ListRequest {
func requestList() -> Single<Moya.Response> {
homeProvider.rx.request(HomeService.list)
}
}
/// 他人主页接口
protocol OtherInfoRequest: BaseRequestable {
func requestOtherInfo() -> Single<Moya.Response>
}
extension OtherInfoRequest {
func requestOtherInfo() -> Single<Moya.Response> {
homeProvider.rx.request(HomeService.otherInfo)
}
}
/// 关注与取消接口
protocol AttentionRequest: BaseRequestable {
func requestAction(type:ActionType) -> Single<Moya.Response>
}
extension AttentionRequest {
func requestAction(type:ActionType) -> Single<Moya.Response> {
homeProvider.rx.request(HomeService.attention(type))
}
}
而后不同ViewModel直接将不同的Request当作最小工件装配即可:
swift
class 关注列表ViewModel: ListRequest, AttentionRequest{
}
class 他人主页ViewModel: OtherInfoRequest, AttentionRequest {
}
有没有其他的方式?
上面这种思路是将每一个接口都当作一个独立的protocol去实现,虽然解耦的很不错,但是这样写起来有点费手,而且如果一个ViewModel中有5个甚至更多的接口请求,那么就会变成这样:
swift
class ViewModel: Request1, Request2, Request3, Request4, Request5... {
}
如果熟悉前端或者Retrofit自动化生成,你会发现它们会把所有的接口集中放在一起维护:
swift
protocol RequestProtocol {
static func requestList() -> Single<Moya.Response>
static func requestOtherInfo() -> Single<Moya.Response>
static func requestAction(type:ActionType) -> Single<Moya.Response>
}
extension RequestProtocol {
static func requestList() -> Single<Moya.Response> {
homeProvider.rx.request(HomeService.list)
}
static func requestOtherInfo() -> Single<Moya.Response> {
homeProvider.rx.request(HomeService.otherInfo)
}
static func requestAction(type:ActionType) -> Single<Moya.Response> {
homeProvider.rx.request(HomeService.attention(type))
}
}
enum Request: RequestProtocol {}
不同的ViewModel,根据需要调用这个Request的方法即可:
swift
class 关注列表ViewModel {
func getList() {
Request.requestList()
}
}
class 他人主页ViewModel {
func getOtherInfo() {
Request.requestOtherInfo()
}
}
这样的好处是所有的接口都集中管理在一个protocol中,减少每个接口一个protocol的维护量,过于分散的问题。
思考与总结
在Request中是否需要进行数据转换?
我理解,网络请求就是一个单一的功能,返回数据即可,而涉及的转换,判断都不应该在这一层进行处理,所以最后返回的结果就是Single<Moya.Response>或者Moya.Response即可。
Moya与App之间到底应该如何分层?

这个是Moya官方给出的示例图,Moya封装了Alamofire的细节之后,App其实就可以直接调用了:
swift
import Moya
enum HomeService {
}
extension HomeService: TargetType {
}
let homeProvider = MoyaProvider<HomeService>(plugins: plugins)
homeProvider
其实不管在任何你想要调用的位置都可以使用。
只是,在目前流行的MVVM中,更倾向于将homeProvider
在ViewModel中调用罢了,这里我们不过是将ViewModel层中的调用做了更加精细化的处理,这样做的好处是:
- 可以将网络请求与ViewModel进行解耦
- 可以将网络请求进行复用
- 可以将网络请求进行组合
- 可以将网络请求进行分层
- 可以将网络请求进行单元测试
- 可以将网络请求进行Mock
如果去查阅GetX在Flutter中的使用,你会发现,Page与Controller的关系亦是如此,Page只关心页面的构建,所有的逻辑都是Controller中,而在Controller中又将网络请求独立处理,当然这是一个题外话了,我们有空再聊。