iOS 使用Moya网络请求

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() }
    //}

}

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 数字证书原理 - 无恙 - 博客园 文中首先解释了加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明...
    拉肚阅读 1,656评论 0 3
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 目录 准备 分析2.1. 三次握手2.2. 创建 HTTP 代理(非必要)2.3. TLS/SSL 握手2.4. ...
    RunAlgorithm阅读 37,837评论 12 117
  • 原文地址 http://blog.csdn.net/u012409247/article/details/4985...
    0fbf551ff6fb阅读 3,510评论 0 13