moya用途 swift_swift moya - CSDN
  • moya + RxSwift 进行网络请求

    千次阅读 2017-03-14 14:16:17
    1.关于moya如在OC中使用AFNetworking一般,Swift我们用Alamofire来做网络库.而Moya在Alamofire的基础上又封装了一层:官方说moya有以下特性(我也就信了): 编译时检查正确的API端点访问. 使你定义不同端点枚举值对应...

    1.关于moya

    如在OC中使用AFNetworking一般,Swift我们用Alamofire来做网络库.而Moya在Alamofire的基础上又封装了一层:

    moya

    官方说moya有以下特性(我也就信了):

    • 编译时检查正确的API端点访问.
    • 使你定义不同端点枚举值对应相应的用途更加明晰.
    • 提高测试地位从而使单元测试更加容易.

    2.开始

    1.创建枚举API

    就像这样:

    enum APIManager {
        case getNewsLatest//获取最新消息
        case getStartImage// 启动界面图像获取
        case getVersion(String)//软件版本查询
        case getThemes//主题日报列表查看
        case getNewsDetail(Int)//获取新闻详情
    }
    

    2.实现TargetType协议

    就像这样:

    extension APIManager: TargetType {
    
        /// The target's base `URL`.
        var baseURL: URL {
    
            return URL.init(string: "http://news-at.zhihu.com/api/")!
        }
    
        /// The path to be appended to `baseURL` to form the full `URL`.
        var path: String {
    
            switch self {
    
            case .getNewsLatest:
                return "4/news/latest"
    
            case .getStartImage://start-image 后为图像分辨率,接受任意的 number*number 格式, number 为任意非负整数,返回值均相同。
                return "4/start-image/1080*1776"
    
            case .getVersion(let version)://URL 最后部分的数字代表所安装『知乎日报』的版本
                return "4/version/ios/" + version
    
            case .getThemes:
                return "4/themes"
    
            case .getNewsDetail(let id):
                return "4/news/\(id)"
    
            }
    
    
        }
    
        /// The HTTP method used in the request.
        var method: Moya.Method {
    
            return .get
        }
    
        /// The parameters to be incoded in the request.
        var parameters: [String: Any]? {
    
            return nil
        }
    
        /// The method used for parameter encoding.
        var parameterEncoding: ParameterEncoding {
    
            return URLEncoding.default
        }
    
        /// Provides stub data for use in testing.
        var sampleData: Data {
    
            return "".data(using: String.Encoding.utf8)!
        }
    
        /// The type of HTTP task to be performed.
        var task: Task {
    
            return .request
        }
    
        /// Whether or not to perform Alamofire validation. Defaults to `false`.
        var validate: Bool {
    
            return false
        }
    
    }
    

    在这里,可以设置请求的参数,例如url……method……para等.

    3.使用

    Moya的使用非常简单,通过TargetType协议定义好每个target之后,就可以直接使用Moya开始发送网络请求了。就像这样:

    let provider = MoyaProvider<APIManager>()
            provider.request(.getNewsLatest) { result in
                // do something with result
            }

    3.配合RxSwift

    Moya本身已经是一个使用起来非常方便,能够写出非常简洁优雅的代码的网络封装库,但是让Moya变得更加强大的原因之一还因为它对于Functional Reactive Programming的扩展,具体说就是对于RxSwiftReactiveCocoa的扩展,通过与这两个库的结合,能让Moya变得更加强大。我选择RxSwift的原因有两个,一个是RxSwift的库相对来说比较轻量级,语法更新相对来说比较少,我之前用过ReactiveCocoa,一些大版本的更新需求重写很多代码,第二个更重要的原因是因为RxSwift背后有整个ReactiveX的支持,里面包括JavaJS.Net, SwiftScala,它们内部都用了ReactiveX的逻辑思想,这意味着你一旦学会了其中的一个,以后可以很快的上手ReactiveX中的其他语言。

    Moya提供了非常方面的RxSwift扩展:

    let provider = RxMoyaProvider<APIManager>()
    provider.request(.getNewsLatest)
        .filterSuccessfulStatusCodes()
        .mapJSON()
        .subscribe(onNext: { (json) in
            //do something with posts
            print(json)
         })
         .addDisposableTo(disposeBag)

    解释一下:

    • RxMoyaProviderMoyaProvider的子类,是对RxSwift的扩展

    • filterSuccessfulStatusCodes()MoyaRxSwift提供的扩展方法,顾名思义,可以得到成功地网络请求,忽略其他的

    • mapJSON()也是Moya RxSwift的扩展方法,可以把返回的数据解析成 JSON 格式

    • subscribe 是一个RxSwift的方法,对经过一层一层处理的 Observable 订阅一个 onNextobserver,一旦得到 JSON 格式的数据,就会经行相应的处理

    • addDisposableTo(disposeBag)RxSwift 的一个自动内存处理机制,跟ARC有点类似,会自动清理不需要的对象。

    4.配合HandyJSON

    在实际应用过程中网络请求往往紧密连接着数据层(Model),具体地说,在我们的这个例子中,一般我们需要建立一个类用来统一管理数据,然后把得到的 JSON 数据映射到数据层(Model)。

    struct MenuModel: HandyJSON {
        var others: [ThemeModel]?
    
    }
    
    struct ThemeModel: HandyJSON {
    
        var color: String?
        var thumbnail: String?
        var id: Int?
        var description: String?
        var name: String?
    }
    

    然后创建ViewModel类,创建具体请求方法:

    class MenuViewModel {
    
        private let provider = RxMoyaProvider<APIManager>()
        var dispose = DisposeBag()
    
        func getThemes(completed: @escaping (_ menuModel: MenuModel) -> ()){
    
             provider
                .request(.getThemes)
                .mapModel(MenuModel.self)
                .subscribe(onNext: { (model) in
    
                    completed(model)
                }, onError: { (error) in
    
                }, onCompleted: nil, onDisposed: nil).addDisposableTo(dispose)
    
        }
    
    }
    

    这里解释一下:
    我这里是将请求的数据通过闭包传了出去,当然也可以不那么做.个人喜好问题..

    这里是为 RxSwift 中的 ObservableTypeResponse写一个简单的扩展方法 mapModel,利用我们写好的Model 类,一步就把JSON数据映射成 model

    extension ObservableType where E == Response {
        public func mapModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {
            return flatMap { response -> Observable<T> in
                return Observable.just(response.mapModel(T.self))
            }
        }
    }
    
    extension Response {
        func mapModel<T: HandyJSON>(_ type: T.Type) -> T {
            let jsonString = String.init(data: data, encoding: .utf8)
            return JSONDeserializer<T>.deserializeFrom(json: jsonString)!
        }
    }
    

    5.配合ObjectMapper

    毕竟将json数据转换成model的库那么多 ….,所以……,用哪个很随意…..这里再介绍一下ObjectMapper

    1.创建model类

    class DetailModel: Mappable {
    
        var body = String()
        var image_source: String?
        var title = String()
        var image: String?
        var share_url = String()
        var js = String()
        var recommenders = [[String: String]]()
        var ga_prefix = String()
        var section: DetailSectionModel?
        var type = Int()
        var id = Int()
        var css = [String]()
    
    
    
    
    
        func mapping(map: Map) {
    
            body <- map["body"]
            image_source <- map["image_source"]
            title <- map["title"]
            image <- map["image"]
            share_url <- map["share_url"]
            js <- map["js"]
            recommenders <- map["recommenders"]
            ga_prefix <- map["ga_prefix"]
            section <- map["section"]
            type <- map["type"]
            id <- map["id"]
            css <- map["css"]
        }
        required init?(map: Map) {
    
        }
    }
    

    使用 ObjectMapper ,需要让自己的 Model 类使用 Mappable 协议,这个协议包括两个方法:

    required init?(map: Map) {}
    
    func mapping(map: Map) {}

    mapping 方法中,用 <- 操作符来处理和映射你的 JSON数据。

    数据类建立好之后,我们还需要为 RxSwift 中的 Observable 写一个简单的扩展方法 mapObject,利用我们写好的model 类,一步就把JSON 数据映射成一个个 model

    extension Observable {
        func mapObject<T: Mappable>(type: T.Type) -> Observable<T> {
            return self.map { response in
                //if response is a dictionary, then use ObjectMapper to map the dictionary
                //if not throw an error
                guard let dict = response as? [String: Any] else {
                    throw RxSwiftMoyaError.ParseJSONError
                }
    
                return Mapper<T>().map(JSON: dict)!
            }
        }
    
        func mapArray<T: Mappable>(type: T.Type) -> Observable<[T]> {
            return self.map { response in
                //if response is an array of dictionaries, then use ObjectMapper to map the dictionary
                //if not, throw an error
                guard let array = response as? [Any] else {
                    throw RxSwiftMoyaError.ParseJSONError
                }
    
                guard let dicts = array as? [[String: Any]] else {
                    throw RxSwiftMoyaError.ParseJSONError
                }
    
                return Mapper<T>().mapArray(JSONArray: dicts)!
            }
        }
    }
    
    enum RxSwiftMoyaError: String {
        case ParseJSONError
        case OtherError
    }
    
    extension RxSwiftMoyaError: Swift.Error { }
    
    • mapObject 方法处理单个对象,mapArray 方法处理对象数组。

    • 如果传进来的数据 response 是一个 dictionary,那么就利用 ObjectMappermap方法映射这些数据,这个方法会调用你之前在 mapping方法里面定义的逻辑。

    • 如果 response 不是一个 dictionary, 那么就抛出一个错误。

    • 在底部自定义了简单的Error,继承了SwiftError类,在实际应用过程中可以根据需要提供自己想要的 Error

    然后运行请求方法:

    class DetailViewModel {
    
        private let provider = RxMoyaProvider<APIManager>()
    
        func getNewsDetail(id: Int) -> Observable<DetailModel> {
    
            return provider
                .request(.getNewsDetail(id))
                .filterSuccessfulStatusCodes()
                .mapJSON()
                .mapObject(type: DetailModel.self)
    
        }
    }
    

    有没有感觉很爽呢!————源码地址,共同学习!

    原文地址

    有不对之处,,,,还望各路大神不吝指正!

    展开全文
  • Moya官方文档翻译

    2019-02-28 09:10:36
    原文地址:https://github.com/Moya/Moya/tree/master/docs ...endpoint是Moya用来推断最终将要进行的网络请求的半内部数据结构。 endpoint存储以下数据: The url. The HTTP method (GET, POST, ...

    原文地址:https://github.com/Moya/Moya/tree/master/docs 参考文章:http://www.jianshu.com/p/c1494681400b

    Endpoints功能翻译

    endpoint是Moya用来推断最终将要进行的网络请求的半内部数据结构。 endpoint存储以下数据:

    • The url.
    • The HTTP method (GET, POST, etc).
    • The request parameters.
    • The parameter encoding (URL, JSON, custom, etc). 参数编码
    • The HTTP request header fields. 请求头
    • The sample response (for unit testing). 测试数据返回

    Providers将Targets的数据解析Endpoint里面,再将Endpoint解析到实际的网络请求中. 这里有两种方式和Endpoint交互 1.当创建provider时,可以指定从Target到Endpoint的映射方式。 2.当创建provider时,可以指定从Endpoint到URLRequest的映射方式。 第一条类似于以下内容

    let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
        let url = target.baseURL.appendingPathComponent(target.path).absoluteString
        return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)
    }
    复制代码

    这实际上是Moya提供的默认实现。 如果您需要某些自定义内容,例如,如果您的API需要自定义参数解析,或者如果您要创建一个在单元测试中返回非200 HTTP状态的测试提供程序,这就是你可以修改的地方。 第二种使用是非常罕见的。 Moya试图阻止你不必担心低级细节。如果你需要它也可以进行修改。 其使用在下面进一步描述。 让我们来看一个提供从Target到Endpoint灵活解析的示例。

    从Target 到 Endpoint

    默认情况下,Endpoint实例使用URLEncoding.default类型参数编码。 您可以在设置提供程序时使用endpointClosure中的Endpoint初始值设置的可选parameterEncoding参数指定在endpointClosure中逐个目标地编码参数的方式。 有三种参数编码类型:URL编码,JSON编码,PropertyList编码,它直接映射到Alamofire中的相应类型.每个类型都有.default属性,它给你一个特定的ParameterEncoding类型的默认实例。此外,如果你想创建你的自定义类型,只是实现ParameterEncoding协议就可以了.通常你只是想要URLEncoding.default,但你可以使用你喜欢的。这些直接映射到Alamofire参数编码。 如果你想获得关于ParameterEncoding类型的更多信息以及如何创建自己的,请查看Alamofire的关于这个问题的文档。 您可以在此闭包中添加参数或HTTP头字段。 例如,我们可能希望在HTTP头字段中设置我们的应用程序名称供服务端分析

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

    这也意味着您可以向部分或所有endPoint提供额外参数。 例如,假设我们需要假设的MyTarget目标的所有值的认证令牌,除了实际进行认证的目标之外。 我们可以构造类似于以下的endpointClosure。

    let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
        let defaultEndpoint = MoyaProvider.defaultEndpointMapping(target)
    
        // Sign all non-authenticating requests
        switch target {
        case .authenticate:
            return defaultEndpoint
        default:
            return defaultEndpoint.adding(newHttpHeaderFields: ["AUTHENTICATION_TOKEN": GlobalAppStorage.authToken])
        }
    }
    let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)
    复制代码

    注意我们可以依靠Moya的现有行为并扩展 - 而不是替换它。 add(newParameters :)和add(newHttpHeaderFields :)函数允许您依赖现有的Moya代码并添加您自己的自定义值。 示例响应是TargetType协议的要求。 但是它们只指定返回的数据。 目标到端点映射闭包是可以指定更多细节的地方,这对单元测试很有用。 示例响应具有以下两个值之一:

    • NetworkError,带有NSError?可选错误类型。
    • NetworkResponse,具有Int状态代码和Data返回的数据。

    Request Mapping

    正如我们前面提到的,这个库的目的不是真正提供一个编码框架来访问网络 - 这是Alamofire的工作.相反,Moya是一种方式来构思你对网络访问的想法,并提供编译时检查明确定义的网络目标。您已经了解了如何使用MoyaProvider初始值设定器的endpointClosure参数将Targets映射到endpoints。这让你创建一个Endpoint实例,Moya将使用它来推断网络API调用。在某些时候,该Endpoints必须解析成一个实际的URLRequest给Alamofire。这就是requestClosure参数的用途。 requestClosure是一种可选的,用来修改网络的请求的最后方式。它具有默认值MoyaProvider.DefaultRequestMapper,它只使用Endpoint实例的urlRequest属性。 此闭包接收Endpoint实例,并负责调用RequestResultClosure(Result <URLRequest,MoyaError> - > Void的简写)的参数与表示Endpoint的请求。在这里做你的OAuth签名或其他事情。因为你可以异步地调用闭包,你可以使用你喜欢的任何认证库(例子)。除了修改请求,你可以简单地记录它。

    let requestClosure = { (endpoint: Endpoint<GitHub>, done: MoyaProvider.RequestResultClosure) in
        var request = endpoint.urlRequest
    
        // Modify the request however you like.
    
        done(.success(request))
    }
    let provider = MoyaProvider<GitHub>(requestClosure: requestClosure)
    复制代码

    此requestClosure可用于修改特定于URLRequest的属性或向请求提供直到该请求创建成功才能知道的信息,例如Cookie设置。注意上面提到的endpointClosure不是用于这个目的或任何特定于请求的应用程序级映射。 此参数实际上对修改请求对象非常有用。 URLRequest有很多可以自定义的属性。假设您要停用所有要求的Cookie:

    { (endpoint: Endpoint<ArtsyAPI>, done: MoyaProvider.RequestResultClosure) in
        var request: URLRequest = endpoint.urlRequest
        request.httpShouldHandleCookies = false
        done(.success(request))
    }
    复制代码

    您还可以执行网络请求的日志记录,因为此闭包在请求发送到网络之前被调用。

    Providers功能翻译

    当使用Moya时,您通过MoyaProvider实例创建所有API请求,传递一个枚举值,指定您要调用的endpoint。 设置好您的endpoint后,您基本上设置好了基本用法:

    let provider = MoyaProvider<MyService>()
    复制代码

    经过简单的设置,你就可以开始请求了:

    provider.request(.zen) { result in
        // `result` is either .success(response) or .failure(error)
    }
    复制代码

    这就是全部的了! request()方法返回一个Cancellable,它只有一个公共函数cancel(),您可以使用它来取消请求。 有关Result类型的更多信息请参阅示例。 记住如何放置target和provider完全取决于你。 你可以查看Artsy的实现的例子。 但不要忘记在属性中保留它的引用。 如果它被释放,你会看到-999“cancelled”错误响应。

    Advanced Usage

    为了解释MoyaProvider的所有配置选项,我们将在下面的章节逐一介绍每个参数。

    endpointClosure:

    MoyaProvider初始化程序的第一个(可选)参数是一个endpoints闭包,它负责将枚举值映射到具体的Endpoint实例。 让我们来看看具体是什么样子。

    let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
        let url = target.baseURL.appendingPathComponent(target.path).absoluteString
        return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)
    }
    let provider = MoyaProvider(endpointClosure: endpointClosure)
    复制代码

    注意,我们不必在MoyaProvider初始化程序中指定泛型类型,因为Swift将从我们的endpointClosure类型推断它。 这个endpointClosure就像你可以得到的一样简单。 它实际上也是默认实现,存储在MoyaProvider.defaultEndpointMapping。 查看Endpoints文档了解更多有关为什么你需要自定义这些的信息。

    requestClosure:

    下一个可选的初始化参数是requestClosure,它将Endpoint解析为实际的URLRequest。 再次查看Endpoints文档了解如何以及为什么这样做。

    stubClosure:

    下一个选项是提供一个stubClosure。 这将返回.never(默认值),.immediate或.delayed(seconds)之一,您可以将已存根的请求延迟一定的时间。 例如,.delayed(0.2)将延迟每个存根请求。 这可以很好地模拟单元测试中的网络延迟。 很好的是如果你需要不同于其他请求来存根一些请求,你可以使用自己的闭包。

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

    但通常你想要对所有的目标执行同样的stubbing行为。 有三个类方法在MoyaProvider你可以使用代替。

    MoyaProvider.neverStub
    MoyaProvider.immediatelyStub
    MoyaProvider.delayedStub(seconds)
    复制代码

    所以在上面的例子中,如果你想要立即改变所有目标的stubbing行为,以下任一将起作用。

    let provider = MoyaProvider<MyTarget>(stubClosure: { (_: MyTarget) -> Moya.StubBehavior in return .immediate })
    let provider = MoyaProvider<MyTarget>(stubClosure: MoyaProvider.immediatelyStub)
    复制代码

    manager

    接下来就是manager参数。 默认情况下,你会得到一个基本配置的自定义Alamofire.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
        return manager
    }
    复制代码

    只有一个特别的事情:因为构造一个Alamofire.Request在AF将默认立即激活请求,即使单元测试的stubbing请求。 因此在Moya中,startRequestsImmediately默认设置为false。 如果您想要自定义自己的管理器,例如要添加SSL绑定,请创建一个并传递进来,所有请求将路由通过自定义配置的管理器。

    let policies: [String: ServerTrustPolicy] = [
        "example.com": .PinPublicKeys(
            publicKeys: ServerTrustPolicy.publicKeysInBundle(),
            validateCertificateChain: true,
            validateHost: true
        )
    ]
    
    let manager = Manager(
        configuration: URLSessionConfiguration.default,
        serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies)
    )
    
    let provider = MoyaProvider<MyTarget>(manager: manager)
    复制代码

    plugins

    最后您还可以向provider提供一个plugins数组。 这些接收在发送请求之前和接收到响应之后回调。 有一些插件已经包括:一个用于网络活动(NetworkActivityPlugin),一个用于记录所有网络活动(NetworkLoggerPlugin),另一个用于HTTP验证。 例如,您可以通过在Endpoint的plugins参数传递[NetworkLoggerPlugin()]来启用logger plugin。 注意,插件也可以是可配置的,例如已经包含的NetworkActivityPlugin需要一个networkActivityClosure参数。 可配置的插件实现如下所示:

    ublic final class NetworkActivityPlugin: PluginType {
    
        public typealias NetworkActivityClosure = (change: NetworkActivityChangeType) -> ()
        let networkActivityClosure: NetworkActivityClosure
    
        public init(networkActivityClosure: NetworkActivityClosure) {
            self.networkActivityClosure = networkActivityClosure
        }
    
        // MARK: Plugin
    
        /// Called by the provider as soon as the request is about to start
        public func willSend(request: RequestType, target: TargetType) {
            networkActivityClosure(change: .began)
        }
    
        /// Called by the provider as soon as a response arrives
        public func didReceive(data: Data?, statusCode: Int?, response: URLResponse?, error: ErrorType?, target: TargetType) {
            networkActivityClosure(change: .ended)
        }
    }
    复制代码

    networkActivityClosure是一个闭包,您可以提供此闭包,以便在网络请求开始或结束时收到通知。 这对于使用网络活动指示器很有用。 注意这个闭包的签名是(change:NetworkActivityChangeType) - >(),因此只有当请求有.began或.end时才会通知您 - 您不会被提供请求本身的任何其他详细信息。

    Plugins功能翻译

    Moya插件用于修改请求和响应或执行副作用。 调用一个插件:

    • (prepare)在Moya已经将TargetType解析为URLRequest之后,这是在发送请求之前修改请求的机会(例如添加头)。
    • (willSend)在请求被发送前,这是检查请求并执行任何副作用(例如日志记录)的机会。
    • (didReceive)这是请求返回后,执行副作用的机会。
    • (process)在完成调用结果之前,这是对请求的结果进行任何修改的机会。

    Built in plugins

    Moya附带一些默认插件,可用于常用功能:身份验证,网络活动指示灯管理和日志记录。在provider初始化的时候你可以简单的声明一个插件:

    let provider = MoyaProvider<GitHub>(plugins: [NetworkLoggerPlugin(verbose: true)])
    复制代码

    Authentication

    认证插件允许用户为每个请求分配一个可选的URLCredential。 收到请求时没有操作。 该插件可以在Sources/Moya/Plugins/CredentialsPlugin.swift找到

    Network Activity Indicator

    iOS网络的一个常见任务是在网络请求期间显示网络活动指示器,并在所有请求完成后将其删除。provided插件添加回调,在请求开始和结束时调用回调,可用于跟踪正在进行的请求数,并相应地显示/隐藏网络活动指示器。 该插件可以在Sources/Moya/Plugins/NetworkActivityPlugin.swift找到

    Logging

    在开发期间,将网络活动记录到控制台非常有用。 这可以是从发送和接收的请求的URL到记录完整头,方法,请求正文等任何东西。 provided的日志插件是最复杂的插件,并且可以配置为适合您的应用程序(和构建类型)所需的日志量。初始化插件时,您可以选择是否verbosity,是否记录curl命令以及提供输出数据的功能(如果您使用自己的日志框架代替print),并在打印之前格式化数据(默认情况下,返回数据将用String.Encoding.utf8转换为字符串,但如果你想转换为完美打印的JSON为你的响应,你可以传入一个formatter函数,请参阅Demo/Shared/GitHubAPI.swift中的函数JSONResponseDataFormatter的一个例子) 该插件可以在Sources/Moya/Plugins/NetworkLoggerPlugin.swift找到

    Custom plugins

    每次您需要在发送请求之前和/或响应后立即执行一些代码,您可以创建一个自定义插件,实现PluginType协议。 有关创建插件的示例,请参阅docs/Examples/CustomPlugin.md and docs/Examples/AuthPlugin.md.

    展开全文
  • iOS 使用Moya网络请求

    2019-06-10 13:07:29
    Moya最新版本11.0.2 由于前段时间写了这篇文章,最新Moya已更新最新版本,故此也更新了下用法,本人已使用,故特意奉上最新的使用demo供参考。 Moya11.0.2Demo Moya简介 Moya 是你的 app 中缺失的网络层。不用再去想...

    Moya最新版本11.0.2

    由于前段时间写了这篇文章,最新Moya已更新最新版本,故此也更新了下用法,本人已使用,故特意奉上最新的使用demo供参考。 Moya11.0.2Demo

    Moya简介

    Moya 是你的 app 中缺失的网络层。不用再去想在哪儿(或者如何)安放网络请求,Moya 替你管理。

    Moya 有几个比较好的特性:

    • 编译时检查正确的API端点访问.

    • 使你定义不同端点枚举值对应相应的用途更加明晰.

    • 提高测试地位从而使单元测试更加容易.

    Swift我们用Alamofire来做网络库.而Moya在Alamofire的基础上又封装了一层,如下流程图说明Moya的简单工作流程图:

    Moya的官方下载地址点我强大的Moya,有具体的使用方法在demo里面有说明。

    本文主要介绍一下Moya的用法

    • 设置请求头部信息
    • 设置超时时间
    • 自定义插件
    • 自签名证书

    注意:以下所出现的NetAPIManager跟官网上demo的** GitHub**是一样类型的文件,都是这个enum实现一个协议TargetType,点进去可以看到TargetType定义了我们发送一个网络请求所需要的东西,什么baseURL,parameter,method等一些计算性属性,我们要做的就是去实现这些东西,当然有带默认值的我们可以不去实现,但是设置头部信息跟超时时间就要修改这些系统默认设置了。

    为了看得更加清楚,贴上NetAPIManager文件的内容

    //
    //  NetAPIManager.swift
    //  NN110
    //
    //  Created by 陈亦海 on 2017/5/12.
    //  Copyright © 2017年 陈亦海. All rights reserved.
    //
    
    import Foundation
    import Moya
    
    
    enum NetAPIManager {
        case Show
        case upload(bodyData: Data)
        case download
        case request(isTouch: Bool, body: Dictionary<String, Any>? ,isShow: Bool)
    }
    
    
    extension NetAPIManager: TargetType {
        var baseURL: URL {//服务器地址
            
            switch self {
            case .request( _, _, _):
                return URL(string: "https://www.pmphmall.com")!
            default:
                return URL(string: "https://httpbin.org")!
            }
            
            
        }
        
        var path: String {//具体某个方法的路径
            switch self {
            case .Show:
                return ""
            case .upload(_):
                return ""
            case .request(_, _, _):
                return "/app/json.do"
            case .download:
                return ""
            }
        }
        
        var method: Moya.Method {//请求的方法 get或者post之类的
            switch self {
            case .Show:
                return .get
            case .request(_, _, _):
                return .post
            default:
                return .post
            }
        }
        
        var parameters: [String: Any]? {//请求的get post给服务器的参数
            switch self {
            case .Show:
                return nil
            case .request(_, _, _):
                return ["msg":"H4sIAAAAAAAAA11SSZJFIQi7EqPAEgTvf6TP62W7sMoSQhKSWDrs6ZUKVWogLwYV7RjHFBZJlNlzloN6LVqID4a+puxqRdUKVNLwE1TRcZIC/fjF2rPotuXmb84r1gMXbiASZIZbhQdKEewJlz41znDkujCHuQU3dU7G4/PmVRnwArMLXukBv0J23XVahNO3VX35wlgce6TLUzzgPQJFuHngAczl6VhaNXpmRLxJBlMml6gdLWiXxTdO7I+iEyC7XuTirCQXOk4dotgArgkH/InxVjfNTnE/uY46++hyAiLFuFL4cv1Z8WH5DgB2GnvFXMh5gm53Tr13vqqrEYtcdXfkNsMwKB+9sAQ77grNJmquFWOhfXA/DELlMB0KKFtHOc/ronj1ml+Z7qas82L3VWiCVQ+HEitjTVzoFw8RisFN/jJxBY4awvq427McXqnyrfCsl7oeEU6wYgW9yJtj1lOkx0ELL5Fw4z071NaVzRA9ebxWXkFyothgbB445cpRmTC+//F73r1kOyQ3lTpec12XNDR00nnq5/YmJItW3+w1z27lSOLqgVctrxG4xdL9WVPdkH1tkiZ/pUKBGhADAAA="]
            default:
                return nil
            
            }
        }
        
        var sampleData: Data { //编码转义
           return "{}".data(using: String.Encoding.utf8)!
        }
        
        var task: Task { //一个请求任务事件
            
            switch self {
    
            
            case let .upload(data):
            return .upload(.multipart([MultipartFormData(provider: .data(data), name: "file", fileName: "gif.gif", mimeType: "image/gif")]))
                
            default:
                return .request
    
           }
    
         }
        
        var parameterEncoding: ParameterEncoding {//编码的格式
            switch self {
            case .request(_, _, _):
                return URLEncoding.default
            default:
                return URLEncoding.default
            }
            
        }
        //以下两个参数是我自己写,用来控制网络加载的时候是否允许操作,跟是否要显示加载提示,这两个参数在自定义插件的时候会用到
        var touch: Bool { //是否可以操作
            
            switch self {
            case .request(let isTouch, _, _):
                return isTouch
            default:
                return false
            }
            
        }
        
        var show: Bool { //是否显示转圈提示
            
            switch self {
            case .request( _, _,let isShow):
                return isShow
            default:
                return false
            }
            
        }
        
        
    }
    
    复制代码

    如何设置Moya请求头部信息

    头部信息的设置在开发过程中很重要,如服务器生成的token,用户唯一标识等 我们直接上代码,不说那么多理论的东西,哈哈

    // MARK: - 设置请求头部信息
    let myEndpointClosure = { (target: NetAPIManager) -> Endpoint<NetAPIManager> in
        
        
        let url = target.baseURL.appendingPathComponent(target.path).absoluteString
        let endpoint = Endpoint<NetAPIManager>(
            url: url,
            sampleResponseClosure: { .networkResponse(200, target.sampleData) },
            method: target.method,
            parameters: target.parameters,
            parameterEncoding: target.parameterEncoding
        )
    
        //在这里设置你的HTTP头部信息
        return endpoint.adding(newHTTPHeaderFields: [
            "Content-Type" : "application/x-www-form-urlencoded",
            "ECP-COOKIE" : ""
            ])
        
    }
    复制代码

    如何设置请求超时时间

    // MARK: - 设置请求超时时间
    let requestClosure = { (endpoint: Endpoint<NetAPIManager>, done: @escaping MoyaProvider<NetAPIManager>.RequestResultClosure) in
        
        guard var request = endpoint.urlRequest else { return }
        
        request.timeoutInterval = 30    //设置请求超时时间
        done(.success(request))
    }
    复制代码

    自定义插件

    自定义插件必须PluginType协议的两个方法willSend与didReceive

    //
    //  MyNetworkActivityPlugin.swift
    //  NN110
    //
    //  Created by 陈亦海 on 2017/5/10.
    //  Copyright © 2017年 CocoaPods. All rights reserved.
    //
    
    import Foundation
    import Result
    import Moya
    
    
    /// Network activity change notification type.
    public enum MyNetworkActivityChangeType {
        case began, ended
    }
    
    /// Notify a request's network activity changes (request begins or ends).
    public final class MyNetworkActivityPlugin: PluginType {
        
        
        
        public typealias MyNetworkActivityClosure = (_ change: MyNetworkActivityChangeType, _ target: TargetType) -> Void
        let myNetworkActivityClosure: MyNetworkActivityClosure
        
        public init(newNetworkActivityClosure: @escaping MyNetworkActivityClosure) {
            self.myNetworkActivityClosure = newNetworkActivityClosure
        }
        
        // MARK: Plugin
        
        /// Called by the provider as soon as the request is about to start
        public func willSend(_ request: RequestType, target: TargetType) {
            myNetworkActivityClosure(.began,target)
        }
        
        /// Called by the provider as soon as a response arrives, even if the request is cancelled.
        public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
            myNetworkActivityClosure(.ended,target)
        }
    }
    
    复制代码

    使用自定义插件方法

    // MARK: - 自定义的网络提示请求插件
    let myNetworkPlugin = MyNetworkActivityPlugin { (state,target) in
        if state == .began {
            //        SwiftSpinner.show("Connecting...")
            
            let api = target as! NetAPIManager
            if api.show {
                print("我可以在这里写加载提示")
            }
            
            if !api.touch {
                print("我可以在这里写禁止用户操作,等待请求结束")
            }
    
            print("我开始请求\(api.touch)")
            
            UIApplication.shared.isNetworkActivityIndicatorVisible = true
        } else {
            //        SwiftSpinner.show("request finish...")
            //        SwiftSpinner.hide()
            print("我结束请求")
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
            
        }
    }
    
    复制代码

    自签名证书

    在16年的WWDC中,Apple已表示将从2017年1月1日起,**所有新提交的App必须强制性应用HTTPS协议来进行网络请求。**默认情况下非HTTPS的网络访问是禁止的并且不能再通过简单粗暴的向Info.plist中添加NSAllowsArbitraryLoads 设置绕过ATS(App Transport Security)的限制(否则须在应用审核时进行说明并很可能会被拒)。所以还未进行相应配置的公司需要尽快将升级为HTTPS的事项提上进程了。本文将简述HTTPS及配置数字证书的原理并以配置实例和出现的问题进行说明,希望能对你提供帮助。(比心~)

    HTTPS: 简单来说,HTTPS就是HTTP协议上再加一层加密处理的SSL协议,即HTTP安全版。相比HTTP,HTTPS可以保证内容在传输过程中不会被第三方查看、及时发现被第三方篡改的传输内容、防止身份冒充,从而更有效的保证网络数据的安全。 HTTPS客户端与服务器交互过程: 1、 客户端第一次请求时,服务器会返回一个包含公钥的数字证书给客户端; 2、 客户端生成对称加密密钥并用其得到的公钥对其加密后返回给服务器; 3、 服务器使用自己私钥对收到的加密数据解密,得到对称加密密钥并保存; 4、 然后双方通过对称加密的数据进行传输。
    数字证书: 在HTTPS客户端与服务器第一次交互时,服务端返回给客户端的数字证书是让客户端验证这个数字证书是不是服务端的,证书所有者是不是该服务器,确保数据由正确的服务端发来,没有被第三方篡改。数字证书可以保证数字证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方身份。证书由公钥、证书主题(Subject)、数字签名(digital signature)等内容组成。其中数字签名就是证书的防伪标签,目前使用最广泛的SHA-RSA加密。 证书一般分为两种:

    1. 一种是向权威认证机构购买的证书,服务端使用该种证书时,因为苹果系统内置了其受信任的签名根证书,所以客户端不需额外的配置。为了证书安全,在证书发布机构公布证书时,证书的指纹算法都会加密后再和证书放到一起公布以防止他人伪造数字证书。而证书机构使用自己的私钥对其指纹算法加密,可以用内置在操作系统里的机构签名根证书来解密,以此保证证书的安全。
    2. 另一种是自己制作的证书,即自签名证书。好处是不需要花钱购2买,但使用这种证书是不会受信任的,所以需要我们在代码中将该证书配置为信任证书.

    自签名证书具体实现: 我们在使用自签名证书来实现HTTPS请求时,因为不像机构颁发的证书一样其签名根证书在系统中已经内置了,所以我们需要在App中内置自己服务器的签名根证书来验证数字证书。首先将服务端生成的.cer格式的根证书添加到项目中,注意在添加证书要一定要记得勾选要添加的targets。这里有个地方要注意:苹果的ATS要求服务端必须支持TLS 1.2或以上版本;必须使用支持前向保密的密码;证书必须使用SHA-256或者更好的签名hash算法来签名,如果证书无效,则会导致连接失败。由于我在生成的根证书时签名hash算法低于其要求,在配置完请求时一直报NSURLErrorServerCertificateUntrusted = -1202错误,希望大家可以注意到这一点。

    那么如何在Moya中使用自签名的证书来实现HTTPS网络请求呢,请期待下回我专门分享......需要自定义一个Manager管理

    综合使用的方法如下

    定义一个公用的Moya请求服务对象

    let MyAPIProvider = MoyaProvider<NetAPIManager>(endpointClosure: myEndpointClosure,requestClosure: requestClosure, plugins: [NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter),myNetworkPlugin])
    
    // MARK: -创建一个Moya请求
    func sendRequest(_ postDict: Dictionary<String, Any>? = nil,
                     success:@escaping (Dictionary<String, Any>)->(),
                     failure:@escaping (MoyaError)->()) -> Cancellable? {
        
       let request = MyAPIProvider.request(.Show) { result in    
            switch result {
            case let .success(moyaResponse):
                
                
                do {
                    let any = try moyaResponse.mapJSON()
                    let data =  moyaResponse.data
                    let statusCode =  moyaResponse.statusCode
                    MyLog("\(data) --- \(statusCode) ----- \(any)")
                    
                    success(["":""])
                    
    
                } catch {
                    
                }
                
               
                
            case let .failure(error):
                
                print(error)
                failure(error)
            }
        }
        
        return request
    }
    复制代码

    取消所有的Moya请求

    // MARK: -取消所有请求
    func cancelAllRequest() {
    //    MyAPIProvider.manager.session.invalidateAndCancel()  //取消所有请求
        MyAPIProvider.manager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
            dataTasks.forEach { $0.cancel() }
            uploadTasks.forEach { $0.cancel() }
            downloadTasks.forEach { $0.cancel() }
        }
        
        //let sessionManager = Alamofire.SessionManager.default
        //sessionManager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
        //    dataTasks.forEach { $0.cancel() }
        //    uploadTasks.forEach { $0.cancel() }
        //    downloadTasks.forEach { $0.cancel() }
        //}
    
    }
    复制代码

    完毕,待续更高级的用法...

    本人的简书上也有发表 Lewis简书 有写的不对的地方,请指正。

    展开全文
  • 相信大家都封装过网络层。 ...所以我们都会倾向于根据自己的实际需求,再封装一个更好用的网络层,加入一些特殊处理。同时也让业务代码更好地与底层的网络框架隔离和解耦。...Moya实际上做的就是这样一件事,它在...

    相信大家都封装过网络层。

    虽然系统提供的网络库以及一些著名的第三方网络库(AFNetworkingAlamofire)已经能满足各种 HTTP/HTTPS的网络请求,但直接在代码里用起来,终归是比较晦涩,不是那么的顺手。所以我们都会倾向于根据自己的实际需求,再封装一个更好用的网络层,加入一些特殊处理。同时也让业务代码更好地与底层的网络框架隔离和解耦。

    Moya实际上做的就是这样一件事,它在 Alamofire的基础上又封装了一层,让我们不必处理过多的底层细节。按照官方文档的说法:

    It's less of a framework of code and more of a framework of how to think about network requests.

    对于应用层开发者来说,一个 HTTP/HTTPS的网络请求流程很简单,即客户端发起请求,服务端接收到请求处理后再将响应数据回传给客户端。对于客户端来说,大体只需要做两件事:构建请求并发送、接收响应并处理。如下一个简单的流程:

     

     

    我们这里从普通数据请求的整个流程来看看 Moya的基本实现。

    操控者 MoyaProvider

    在梳理流程之前,有必要了解一下 MoyaProvider。我把这个 MoyaProvider称为 Moya的操控者。在 Moya层,它是整个数据流的管理者,包括构建请求、发起请求、接收响应、处理响应。也许类似的,我们自己封装的网络库也会有这样一个角色,如 NetworkManager。我们来看看它和 Moya中其它类/结构体的关系。

     

     

    与我们直接打交道最多的也是这个类,不过我们不在这细讲,在这里它不是主角。我们来结合数据流,来看看数据在这个类中怎么流转。

    构建 Request

    一个基本的 HTTP/HTTPS普通数据请求通常包含以下几个要素:

    • URL
    • 请求参数
    • 请求方法
    • 请求报头信息
    • 可选的认证信息

    对于 Alamofire来说,最终是构建一个 Request,然后使用不同的请求对象,依赖于这些信息来发起请求。所以,构建请求的终点是 Request

    不过官方文档给了一个构建 Request的流程图:

     

     

    我们来看看这个流程。

    请求的起点 Target

    Target是构建一个请求的起点,它包含一个请求所需要的基本信息。不过一个 Target不是定义单一一个请求,而是定义一组相关的请求。这里先了解一下 TargetType协议:

    public protocol TargetType {
        var baseURL: URL { get }
        var path: String { get }
        var method: Moya.Method { get }
    
        /// Provides stub data for use in testing.
        var sampleData: Data { get }
    
        var task: Task { get }
        var validationType: ValidationType { get }
        var headers: [String: String]? { get }
    }
    复制代码

    为了控制篇幅,我把不需要的注释都删了,下同。sampleData主要是用于本地 mock数据,在文章中不做描述。

    可以看到这个协议包含了一个请求所需要的基本信息:用于拼接 URL的 baseURL和 path、请求方法、请求报头等。我们自定义的 Target必须实现这个接口,并根据需要设置请求信息,这个应该很好理解。

    如果只是描述一个请求的话,可能使用 struct会好一些;而如果是一组的话,那还是用枚举方便些(话说枚举用得好不好,直接体现了 Swift水平好不好)。来看看官方的例子:

    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")! }
        
        ......
    }
    复制代码

    这基本是标配。枚举的关联对象是请求所需要的参数,如果请求参数过多,最好放在一个字典里面。

    至于 task属性,其类型 Task是一个枚举,定义了请求的实际任务类型,比如说是普通的数据请求,还是上传下载。这个属性可以关注一下,因为请求的参数都是附在这个属性上。

    在扩展 TargetType时,可以根据不同的接口来配置不同的 baseURLpathmethod等信息。不过可能会导致一个问题:在一个大的独立工程里面,通常接口有几十上百个。如果你把所有的接口都放一个枚举里面,你可能最后会发现,各种 switch会把这个文件撑得很长。所以,还需要根据实际情况来看看如何去划分我们的接口,让代码分散在不同的文件里面(MultiTarget专门用来干这事,可以研究一下)。

    到这一步,我们得到的数据是一个 Target枚举,它包含了构建一组请求所需要的信息。实际上,我们主要的任务就是去定义这些枚举,后面的构建过程,如果没有特殊需求,基本上就是个黑盒了。

    有了 Target,我们就可以用具体的枚举值来发起请求了,

    gitHubProvider.request(.userRepositories(username)) { result in
    	......
    }
    复制代码

    大多数时候,业务层代码需要做的就是这些了。是不是很简单?

    下面我们来看看 Moya的黑盒子里面做了些什么?

    Endpoint

    按理说,我们构建好 Target并把对应的信息丢给 MoyaProvider后,MoyaProvider直接去构建一个 Request,然后发起请求就行了。而在从上面的图可以看到,Target和 Request之间还有一个 Endpoint。这是啥玩意呢?我们来看看。

    在 MoyaProvider的 request方法中调用了 requestNormal方法。这个方法的第一行就做了个转换操作,将 Target转换成 Endpoint对象:

    func requestNormal(_ target: Target, callbackQueue: DispatchQueue?, progress: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> Cancellable {
        let endpoint = self.endpoint(target)
        ......
    }
    复制代码

    endpoint()方法实际上调用的是 MoyaProvider的 endpointClosure属性:

    public typealias EndpointClosure = (Target) -> Endpoint
    
    open let endpointClosure: EndpointClosure
    
    open func endpoint(_ token: Target) -> Endpoint {
        return endpointClosure(token)
    }
    复制代码

    EndpointClosure的用途实际上就是将 Target映射为 Endpoint。我们可以自定义转换方法,并在初始 MoyaProvider时传递给 endpointClosure参数,像这样:

    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)
    复制代码

    如果不想自定义,那么就用 Moya提供的默认转换方法就行。

    哦,还没看 Endpoint到底长啥样:

    open class Endpoint {
        public typealias SampleResponseClosure = () -> EndpointSampleResponse
    
        open let url: String
        open let method: Moya.Method
        open let task: Task
        open let httpHeaderFields: [String: String]?
        
        ......
    }
    复制代码

    是不是觉得和 TargetType差不多?那问题来了,为什么要 Endpoint呢?

    我有两个观点:

    1. 比起 Target来,Endpoint更像一个请求对象;Target是通过枚举来描述的一组请求,而 Endpoint就是一个实实在在的请求对象;(废话)
    2. 通过 Endpoint来隔离业务代码与 Request,毕竟这是 Moya的目标

    如果有不同观点,还请告诉我。

    重复上面一句话:我们可以自定义转换方法,来执行 Target到 Endpoint的映射操作。不过还有个问题,有些代码(比如headers的设置)即可以放在 Target里面,也可以放在 Endpoint里面。个人观点是能放在 Target里面的就放在 Target里,这样不需要自已去定义 EndpointClosure

    Endpoint类还有一些方法来便捷创建 Endpoint,可以参考一下。

    到这一步,我们得到的数据是一个 Endpoint对象,有了这个对象,我们就可以来创建 Request了。

    Request

    和 Target->Endpoint的映射一样,Endpoint->Request的映射也有一个类似的属性:requestClosure属性。

    public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void
    
    open let requestClosure: RequestClosure
    复制代码

    同样也可以自定义闭包传递给 MoyaProvider的构造器,但通常不建议这么做。因为这样会让业务代码直接触达 Request,有违 Moya的目标。通常我们直接用默认的转换方法就行。默认映射方法的实现在 MoyaProvider+Defaults.swift文件中,如下:

    public final class func defaultRequestMapping(for endpoint: Endpoint, closure: RequestResultClosure) {
        do {
            let urlRequest = try endpoint.urlRequest()
            closure(.success(urlRequest))
        } 
            
        ......
    }
    复制代码

    看代码会发现实际的转换是由 Endpoint类的 urlRequest方法来完成的,如下:

    public func urlRequest() throws -> URLRequest {
        guard let requestURL = Foundation.URL(string: url) else {
            throw MoyaError.requestMapping(url)
        }
    
        var request = URLRequest(url: requestURL)
        request.httpMethod = method.rawValue
        request.allHTTPHeaderFields = httpHeaderFields
    
        switch task {
        case .requestPlain, .uploadFile, .uploadMultipart, .downloadDestination:
            return request
        case .requestData(let data):
            request.httpBody = data
            return request
            
        ......
    }
    复制代码

    这个方法创建了一个 URLRequest对象,看代码都能理解。

    返回到 defaultRequestMapping()方法中,可以看到生成的 urlRequest被附在一个 Result枚举中,并传给 defaultRequestMapping的第二个参数: RequestResultClosure。这步我们暂时到这。

    到此我们的 URLRequest对象就构建完成了,实际上我们会发现 URLRequest包含的信息并不大,但已经足够了,可以发起请求了。

    发起请求

    我们回到 RequestResultClosure中,也就是 requestNormal()方法的 performNetworking闭包中。在这个闭包里,就开始了发起请求的旅程。我们简单看一下流程:

     

     

    基本上就三个步骤:

    1. performRequest():在这个方法中,将请求根据 task的类型分流;
    2. sendRequest()uploadFile()等四方法:这几个方法主要是创建对应的请求对象,如 DataRequestUploadRequest
    3. sendAlamofireRequest():各类请求最后会汇聚到这个方法中,完成发起请求操作;
    func sendAlamofireRequest<T>(_ alamoRequest: T, target: Target, callbackQueue: DispatchQueue?, progress progressCompletion: Moya.ProgressBlock?, completion: @escaping Moya.Completion) -> CancellableToken where T: Requestable, T: Request {
        ......
    
        progressAlamoRequest = progressAlamoRequest.response(callbackQueue: callbackQueue, completionHandler: completionHandler)
        progressAlamoRequest.resume()
    
    	......
    }
    复制代码

    到此为止,请求部分就基本结束了。

    有一个小问题可以注意下:一个 Target最后一直被传递到 sendAlamofireRequest方法中,比 Endpoint的使用周期还长。呵呵。

    等等,还有件事

    为什么 Target的使用周期比 Endpoint还长呢?看代码,在 sendAlamofireRequest()方法中有这么一段:

    let plugins = self.plugins
    plugins.forEach { $0.willSend(alamoRequest, target: target) }
    复制代码

    也就是说 Target需要用在 plugin的方法中。Plugin,即插件,是 Moya提供了一种特别实用的机制,可以被用来编辑请求、响应及完成副作用的。Moya提供了几个默认的插件,同样我们也可以自定义插件。所有的插件都需要实现 PluginType协议,看看它的定义:

    public extension PluginType {
        func prepare(_ request: URLRequest, target: TargetType) -> URLRequest { return request }
        func willSend(_ request: RequestType, target: TargetType) { }
        func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) { }
        func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError> { return result }
    }
    复制代码

    实际上就是在整个数据流四个位置插入一些操作,这些操作可以对数据进行修改,也可以是一些没有副作用(例如日志)的操作。实际上 prepare操作是在 RequestResultClosure中就执行了。后面两个方法都是在响应阶段插入的操作。在此不描述了。

    总结

    这篇文章主要是从数据的流向来看了看 Moya的请求构建过程。我们避开了各种产生错误的分支以及用于测试插桩的代码,这些有兴趣可以参考代码的具体实现。

    最后盗图一张,你就会发现一图胜千言,我上面讲的以及后面一篇文章讲的全是废话。

     

     

    下一篇我们会从数据流的后半段 -- 响应处理-- 来继续看看 Moya的实现,敬请关注。

    参考

    1. 官方文档 https://github.com/Moya/Moya/blob/master/docs/README.md
    2. Moya的设计之道 https://github.com/LeoMobileDeveloper/Blogs/blob/master/Swift/AnaylizeMoya.md

    追踪一下 Moya 的数据流向,来看看它的基本实现。


    作者:知识小集
    链接:https://juejin.im/post/5ac2cf34f265da23a1421483
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    展开全文
  • Moya 与 RxSwift 使用

    2019-09-27 23:31:24
    如在OC中使用AFNetworking一般,Swift我们用Alamofire来做网络库.而Moya在Alamofire的基础上又封装了一层: 1.关于moya moya 官方说moya有以下特性-_-: 编译时检查正确的API端点访问. 使你定义不同端点枚举值...
  • RxSwift+Moya之项目实战

    千次阅读 2017-10-08 18:59:23
    RxSwift+Moya之项目实战 RxSwift相关基本介绍和用法可参考: RxSwift的使用详解01 RxSwift的使用详解02 一. 下面将将进行实战项目 1.登录注册功能 输入用户名要大于6个字符,不然密码不能输入 密码必须大于6个...
  • Swift 语言指南

    千次阅读 2015-07-03 10:09:06
    这份指南汇集了 Swift 语言主流学习资源,并以开发者的视角整理编排。 GitHub:ipader/SwiftGuide | 网站:http://dev.swiftguide.cn | 欢迎开发者一起维护,或反馈/投稿 想了解关于该指南及 Swift 更多信息...
  • RxSwift+Moya之项目实战 RxSwift相关基本介绍和用法可参考: RxSwift的使用详解01 RxSwift的使用详解02 一. 下面将将进行实战项目 1.登录注册功能 输入用户名要大于6个字符,不然密码不能输入 密码必须大于6个...
1
收藏数 12
精华内容 4
关键字:

moya用途 swift