moya的使用

Moya的使用

关于Moya

Moya是对Alamofire的再次封装。

让我们用一张图来简单来对比一下直接用Alamofire和用moya的区别:

diagram.png

有关Alamofire

为了对Moya有更好的了解。让我们先复习一下Alamofire的用法。

Alamofire的用法

用法一:

let parameters: Parameters = [
    "foo": [1,2,3],
    "bar": [
    "baz": "qux"
]
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding.default)

用法二:

Alamofire.request("https://httpbin.org/get")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseData { response in
    switch response.result {
    case .success:
        print("Validation Successful")
        case .failure(let error):
        print(error)
    }
}

Alamofire请求时输入各种请求的条件(url, parameters, header,validate etc)的时候略显累赘,如果我们要设置默认parameters,还有针对特定API做修改的时候,实现起来就会很费劲。

然后,就有了我们Moya。

Moya的简单实用

Moya的快速上手

Moya是通过POP(面向协议编程)来设计的一个网络抽象库。

moya简单使用的example:

public enum GitHub {
    case zen
    case userProfile(String)
    case userRepositories(String)
}

extension GitHub: TargetType {
    // 略过
}

let provider = MoyaProvider<GitHub>()
provider.request(.zen) { result in
    // `result` is either .success(response) or .failure(error)
}

1. 创建一个Provider

provider是网络请求的提供者,你所有的网络请求都通过provider来调用。我们先创建一个provider。

provider最简单的创建方法:

// GitHub就是一个遵循TargetType协议的枚举(看上面例子)
let provider = MoyaProvider<GitHub>()

让我看看provider是什么:

open class MoyaProvider<Target: TargetType>: MoyaProviderType {
    //略过....
}

provider是一个遵循 MoyaProviderType协议的公开类,他需要传入一个遵循TargetType协议的对象名,这是泛型的常规用法,大家可以自行Google一下

如果我们要创建provider,我们要看看他的构造方法:

/// Initializes a provider.
    public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
                callbackQueue: DispatchQueue? = nil,
                manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
                plugins: [PluginType] = [],
                trackInflights: Bool = false) {

        self.endpointClosure = endpointClosure
        self.requestClosure = requestClosure
        self.stubClosure = stubClosure
        self.manager = manager
        self.plugins = plugins
        self.trackInflights = trackInflights
        self.callbackQueue = callbackQueue
    }
    

provider所有的属性都是有默认值,具体怎么用我们往后再详谈。现在主要是传入一个遵TargetType协议的对象

2. 创建一个遵循TargetType协议的enum

让我们看看TargetType协议有什么:

public protocol TargetType {

    /// The target's base `URL`.
    var baseURL: URL { get }

    /// The path to be appended to `baseURL` to form the full `URL`.
    var path: String { get }

    /// The HTTP method used in the request.
    var method: Moya.Method { get }

    /// Provides stub data for use in testing.
    var sampleData: Data { get }

    /// The type of HTTP task to be performed.
    var task: Task { get }

    /// The type of validation to perform on the request. Default is `.none`.
    var validationType: ValidationType { get }

    /// The headers to be used in the request.
    var headers: [String: String]? { get }
}

具体的使用方法如下:

public enum GitHub {
    case zen
    case userProfile(String)
    case userRepositories(String)
}

extension GitHub: TargetType {
    public var baseURL: URL { return URL(string: "https://api.github.com")! }
    
    // 对应的不同API path
    public var path: String {
        switch self {
        case .zen:
            return "/zen"
        case .userProfile(let name):
            return "/users/\(name.urlEscaped)"
        case .userRepositories(let name):
            return "/users/\(name.urlEscaped)/repos"
        }
    }
    public var method: Moya.Method {
        return .get
    }
    
    // parameters,upload or download
    public var task: Task {
        switch self {
        case .userRepositories:
            return .requestParameters(parameters: ["sort": "pushed"], encoding: URLEncoding.default)
        default:
            return .requestPlain
        }
    }
    
    // 通过statuscode过滤返回内容
    public var validationType: ValidationType {
        switch self {
        case .zen:
            return .successCodes
        default:
            return .none
        }
    }
    
    // 多用于单元测试
    public var sampleData: Data {
        switch self {
        case .zen:
            return "Half measures are as bad as nothing at all.".data(using: String.Encoding.utf8)!
        case .userProfile(let name):
            return "{\"login\": \"\(name)\", \"id\": 100}".data(using: String.Encoding.utf8)!
        case .userRepositories(let name):
            return "[{\"name\": \"\(name)\"}]".data(using: String.Encoding.utf8)!
        }
    }
    public var headers: [String: String]? {
        return nil
    }
}

TargetType的设计理念是,先创建一个enum,如Github,那代表是你的服务器,case1,case2,case3代表各个API,这样就能统一处理,还可以针对个别API做不同的处理。

设置好了配置,就可以简单创建一个Provider了:

let provider = MoyaProvider<GitHub>()

3. 网络请求方法

创建好了Provider,我们就可以直接调用网络请求了:

 gitHubProvider.request(.zen) { result in
            var message = "Couldn't access API"
            if case let .success(response) = result {
                let jsonString = try? response.mapString()
                message = jsonString ?? message
            }

            self.showAlert("Zen", message: message)
        }

request() 方法返回一个Cancellable, 它有一个你可以取消request的公共的方法。

4. Result

网络请求有个回调,回调一个Result类型的数据:

Result<Moya.Response, MoyaError>

再看看具体定义:

public enum Result<Value, Error: Swift.Error>: ResultProtocol, CustomStringConvertible, CustomDebugStringConvertible {

    case success(Value)
    
    case failure(Error)
    
    //其他略过
}

这是一个枚举,通过枚举获取对应valueerror

这也是一个泛型的经典用法,其中 Value 对应 Moya.ResponseError 对应 MoyaError

 gitHubProvider.request(.zen) { result in
            switch self {
                case let .success(response):
                   let json = try response.mapJSON()
                    print("\(json)");
                    
                case let .failure(error):
                    break;
                }
        }

Moya.Responsepublic final class, 里面有一些好用的方法:

// 转换为Image
func mapImage() throws -> Image;

// 转换为Json
func mapJSON(failsOnEmptyData: Bool = true) throws -> Any;

// 装换为String
func mapString(atKeyPath keyPath: String? = nil) throws -> String;

// 转换为对应的model
func map<D: Decodable>(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) throws -> D;

有关Moya.ResponseMoyaError大家可自行看看源码,有很多好用的属性及方法。

Moya的高级用法

Moya实现了网络层的高度抽象,它是通过以下管道来实现这一点的:

moya_request.png

让我们回顾一下,Provider的构造方法:

/// Initializes a provider.
    public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
                callbackQueue: DispatchQueue? = nil,
                manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
                plugins: [PluginType] = [],
                trackInflights: Bool = false) {

        self.endpointClosure = endpointClosure
        self.requestClosure = requestClosure
        self.stubClosure = stubClosure
        self.manager = manager
        self.plugins = plugins
        self.trackInflights = trackInflights
        self.callbackQueue = callbackQueue
    }
    

Provider输入的参数包括:EndpointClosure,RequestClosure,StubClosure, callbackQueue,plugins, trackInflights

Endpoints

ProviderTargets 映射成 Endpoints, 然后再将 Endpoints 映射成真正的 Request

EndpointClosure = (Target) -> Endpoint就是定义如何将 Targets 映射为Endpoints `

在这个闭包中,你可以改变taskmethodurl, headers 或者 sampleResponse。比如,我们可能希望将应用程序名称设置到HTTP头字段中,从而用于服务器端分析。

let endpointClosure = { (target: MyTarget) -> Endpoint in
    let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
    return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"])
}
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)

requestClosure

前面endpointClosure会把target映射为endpoint, Moya会把endpoint转换为一个真正的Request

RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void 就是 Endpoint 转换为 Request的一个拦截,它还可以修改请求的结果( 通过调用RequestResultClosure = (Result<URLRequest, MoyaError>) )

let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
    do {
        var request = try endpoint.urlRequest()
        // Modify the request however you like.
        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error)))
    }

}
let provider = MoyaProvider<GitHub>(requestClosure: requestClosure)

stubClosure

下一个选择是来提供一个stubClosure。这个闭包返回 .never (默认的), .immediate 或者可以把stub请求延迟指定时间的.delayed(seconds)三个中的一个。 例如, .delayed(0.2) 可以把每个stub 请求延迟0.2s. 这个在单元测试中来模拟网络请求是非常有用的。

更棒的是如果您需要对请求进行区别性的stub,那么您可以使用自定义的闭包。

let provider = MoyaProvider<MyTarget>(stubClosure: { target: MyTarget -> Moya.StubBehavior in
    switch target {
        /* Return something different based on the target. */
    }
})

manager

Provider里面你可以自定义一个 Alamofire.Manager实例对象。

// 这是Moya默认的manager
public final class func defaultAlamofireManager() -> Manager {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders

    let manager = Alamofire.Manager(configuration: configuration)
    manager.startRequestsImmediately = false //设定false,为了单元测试
    return manager
}

用法如下:

let userModuleProvider = MoyaProvider<UserModule>(manager:yourManager)

plugins(插件)

最后, 您可能也提供一个plugins数组给provider。 这些插件会在请求被发送前及响应收到后被执行。 Moya已经提供了一些插件: 一个是 网络活动(NetworkActivityPlugin),一个是记录所有的 网络活动 (NetworkLoggerPlugin), 还有一个是 HTTP Authentication。

plugins里面的对象都遵循协议PluginType, 协议了规定了几种方法,阐述了什么时候会被调用。

public protocol PluginType {
    /// modified Request 请求发送之前调用(主要用于修改request)
    /// Called to modify a request before sending.
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest

    /// Request 请求发送之前调用
    /// Called immediately before a request is sent over the network (or stubbed).
    func willSend(_ request: RequestType, target: TargetType)

    /// 接收到了response,completion handler 之前调用
    /// 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)

    /// completion handler 之前调用(主要用于修改result)
    /// Called to modify a result before completion.
    func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
}

Moya已经有了一个NetworkActivityPlugin:

public final class NetworkActivityPlugin: PluginType {

    public typealias NetworkActivityClosure = (_ change: NetworkActivityChangeType, _ target: TargetType) -> Void
    let networkActivityClosure: NetworkActivityClosure

    public init(networkActivityClosure: @escaping NetworkActivityClosure) {
        self.networkActivityClosure = networkActivityClosure
    }

    public func willSend(_ request: RequestType, target: TargetType) {
        networkActivityClosure(.began, target)
    }

    public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
        networkActivityClosure(.ended, target)
    }
}

它的用法也好简单:

static var plugins: [PluginType] {
        let activityPlugin = NewNetworkActivityPlugin { (state, targetType) in
            switch state {
            case .began:
                if targetType.isShowLoading { //这是我扩展的协议
                    // 显示loading
                }
            case .ended:
                if targetType.isShowLoading { //这是我扩展的协议
                    // 关闭loading
                }
            }
        }
        
        return [
            activityPlugin, myLoggorPlugin
        ]
    }
    
 let userModuleProvider = MoyaProvider<UserModule>(plugins: plugins)
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容