Swift:Moya中的Plugin和面向协议编程

5,128 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

这两天在看看自己的项目中Moya的插件可不可以优化一下。

有的时候看见Moya这个库,就会想究竟是怎么的思维迸发才会想到这样编写网络请求库。

我们总是在谈Alamofire,固然0→1是一个无比艰难又伟大的壮举。

不过我觉得Moya就是一个1→100的过程,它让Swift的网络请求前无古人后无来者。

PluginType协议

我们先来看看Moya中的PluginType协议源码:

/// A Moya Plugin receives callbacks to perform side effects wherever a request is sent or received.
///
/// for example, a plugin may be used to
///     - log network requests
///     - hide and show a network activity indicator
///     - inject additional information into a request
public protocol PluginType {

    /// Called to modify a request before sending.
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest

    /// Called immediately before a request is sent over the network (or stubbed).
    func willSend(_ request: RequestType, target: TargetType)

    /// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
    func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)

    /// Called to modify a result before completion.
    func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>

}

Moya的插件机制实际上就是一个遵守协议的机制,如果自己想写一个Moya插件,那么遵守这个协议,并实现就好了,而且这个协议也做了默认实现,也就是说这么写也是可以的,就是没什么意义:

class LoadingPlugin: PluginType {

}

Moya的内置Plugin——NetworkActivityPlugin和NetworkLoggerPlugin

其实Moya已经很贴心的给我们准备了两个非常好用的插件,NetworkActivityPlugin和NetworkLoggerPlugin。从名字上面我们就可以看出其用途。

NetworkActivityPlugin

这个插件的用途就是用于在网络请求前后,添加loading页面的,我们看看插件的具体代码就可以知道:

/// Called by the provider as soon as the request is about to start
public func willSend(_ request: RequestType, target: TargetType) {
    networkActivityClosure(.began, target)
}

/// Called by the provider as soon as a response arrives, even if the request is canceled.
public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
    networkActivityClosure(.ended, target)
}

在初始化插件的时候,通过判断began和ended的状态,去增加loading与去掉loading。

可以说是,通过插件的方式,让网络请求的数据层面和UI的页面变化进行了隔离,互不干扰,也便于维护。

NetworkLoggerPlugin

说到网络请求,在App开发中不可避免的就是打印JSON。

在Alamofire中,其内部就有一个打印的方案,是通过Notification实现的,而在Moya中是通过Plugin实现的。

这个功能没什么特别说明的,直接集成就好。

插件使用

activityPlugin:

let activityPlugin = NetworkActivityPlugin { (state, targetType) in
    switch state {
    case .began:
            // 显示loading,自行补全逻辑
    case .ended:
            // 关闭loading,自行补全逻辑
    }
}

loggerPlugin:

let loggerPlugin = NetworkLoggerPlugin()

在provider中集成:

let myProvider = MoyaProvider<MyService>(plugins: [loggerPlugin, activityPlugin])

一个Plugin一个功能

其实从本质上,完全可以把loading的业务和logger的打印放在一个Plugin中完成,那么为何Moya要拆分成为2个。

这里我认为是,Moya更倾向于把功能拆解成为一个个小功能,并且让每个小功能都进行解耦,这样的话,在使用中的取舍中更加灵活。

比如我只想在Debug模式下打印JSON,而Release模式下不打印,那么loggerPlugin我在Release下不使用即可。

记住一个Plugin,一个功能。

PluginType协议与拦截器

看着PluginType协议的函数名称和注释:

  • prepare:Called modify a request before sending

  • willSend:Called immediately before a request is sent over the network (or stubbed)

  • didReceive:Called after a response has been received, but before the MoyaProvider has invoked its completion handler

  • process:Called to modify a result before completion.

其实描述的就是网络请求的声明周期中,可以搞点事情嘛,这不就是Android、Flutter开发中的网络请求的拦截器嘛!

下面的代码是Dart中的来自Dio:

extension AddPrettyPrint on Dio {
  Dio get addPrettyPrint {
    this.interceptors.add(
          PrettyDioLogger(
            requestHeader: false,
            requestBody: true,
            responseBody: true,
            responseHeader: false,
            compact: false,
          ),
        );
    return this;
  }
}

static Dio _dio = Dio(
    BaseOptions(
      baseUrl: Api.baseUrl,
      connectTimeout: timeout,
      receiveTimeout: timeout,
      headers: {},
    ),
  ).addPrettyPrint;

这段代码就是为Dio添加了一个PrettyDioLogger拦截器,然后在网络请求的request和response的时候可以打印相关网络请求的信息,是不是和Moya的NetworkLoggerPlugin非常相似呢?

面向协议编程

Moya是一个面向协议编程非常好的蓝本,而我觉得面向协议的核心在于:

  • 只关心这个方法实现了没有,至于怎么实现,不关心

    实现变成了Coder自己的事情,灵活性强。

  • 用强有力的约束让Coder按照规则做事,不遵守规则,对不起,报错

    约束变成了铁律,减少编码错误。

  • 协议可以看成是变相的多继承

    展平类型与类型的区别,可以通过协议类型去约束泛型与函数类型,使得编程更加简洁与灵活,同时也增加了理解难度。

有的时候,写面向协议编程久了,也会怀念面向对象编程。

为何,因为面向协议编程没有继承。

比如我有一个A业务,有一个B业务,其中B业务就是比A业务多一个方法。

面向对象话class B: A,而面向协议struct A: Protocol, struct B: Protocol

struct没有办法继承,页面B明明只是在页面A的基础上加一点功能,B只能在Copy一份A代码后,再加功能。

这里我并不想鼓吹面向协议编程或者面向对象编程,存在即合理,灵活运用才是关键。

再说面向协议编程也不是Swift独树一帜,看看隔壁Dart就知道了,extends、implements、with应有尽有,而我们只有一个:

class PAAppBar extends StatelessWidget implements PreferredSizeWidget {

}

参考文档

moya的使用

总结

本文从Moya抽象的PluginType协议聊到具体的两个Plugin:NetworkActivityPlugin和NetworkLoggerPlugin,并讲解了其使用方法。

对比了PluginType协议和Dio拦截器的一些异同。

最后我们又回到了协议本身,说明了面向协议编程的特点。

希望对大家有所帮助。