swift——项目的基础架构之网络篇—Moya

最近要做新项目,原来的项目框架老了也该升级了。了解了下Moya,发现异常的好用。很好的把接口,接口所需参数,网络请求时的自定义设置等集中到了一起。修改和增加都非常清晰。

导入就不说了,贴上Github网址——Moya

Moya的基本思想

我们希望一些网络抽象层能够直接充分地封装实际调用Alamofire。这应该是简单的,常见的事情是容易的,但足够全面,复杂的事情也很容易。

如果你使用Alamofire来抽象出来URLSession,为什么不用东西去抽象出URL,参数等等呢?

Moya的一些很棒的功能:

  • 编译时检查正确的API端点访问。
  • 让您定义具有相关枚举值的不同端点并且清晰的使用。
  • 将测试桩作为一流公民,使单元测试非常简单。

使用Moya

首先创建一个枚举来存放你所有的API

//API 参数列表
enum bsAPI {
    case getRequest
    case Login(phoneNumber: String, password: String)
    case testNetwork
}

对你的枚举(bsAPI)进行扩展,并遵循TargetType协议。这里面会把请求的网址,路径参数,基本参数,请求类型等等全部定义好。

extension bsAPI: TargetType {
    
    //网址
    var baseURL: URL{
        return URL.init(string: Using_URL)!
    }
    
    //内容拼接
    var path:String{
        return ""
    }
    
    var method: Moya.Method{
        switch self {
        case .getRequest:
            return .get
        default:
            return .post
        }
    }
    
    var parameters: [String: Any]?{
        
        switch self {
        case .Login(let phoneNumber, let password):
            let words = EncodeAction(dic: ["phoneNumber":phoneNumber, "password":password], key: FUNC_Login.md5())  /// 这是我的加密方法,自己Demo就随便一个字典就好
            return ["func":"", "words":words]
            
        case .testNetwork:
            let words: String = EncodeAction(dic: Dictionary<String, Any>(), key: FUNC_Test.md5())  /// 这是我的加密方法,自己Demo就随便一个字典就好
            return ["words":words]
            
        default:
            return nil
        }
    }
    
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    /// Provides stub data for use in testing.
    var sampleData: Data {
        return "".data(using: .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
    }
}

协议定义好了之后使用就异常简单, 定义一个类型就可以开始使用了。

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

但是这是最基础的用法,MoyaProvider是提供很多属性的,下面的写法就为你的网络请求添加了请求时状态栏转圈(状态栏菊花加载)和网络请求页面菊花加载。这样就不用每个请求的时候都带上MBProgressHUD的使用了。

public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
                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
    }

这是MoyaProvider的默认初始化方法,我们会对requestClosure和plugins这两个属性作出自定义。

requestClosure的自定义

let requestTimeoutClosure = { (endpoint: Endpoint<bsAPI>, done: @escaping MoyaProvider<bsAPI>.RequestResultClosure) in
    
    guard var request = endpoint.urlRequest else { return }
    request.timeoutInterval = 10    //设置请求超时时间
    done(.success(request))
    
}

plugins的自定义

let networkPlugin = NetworkActivityPlugin { (type) in
    switch type {
    case .began:
        /// 状态栏转圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        
    case .ended:
        /// 状态栏停止转圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }
}

这里用到的是Moya的网络监控插件,NetworkActivityPlugin,继承自PluginType。定义了状态栏的网络加载。下面要自定义一个继承自PluginType,网络请求时页面加载的插件。

class RequestLoadingPlugin: PluginType {
    
    var HUD:MBProgressHUD = MBProgressHUD.init()
    
    func willSend(_ request: RequestType, target: TargetType) {
        print("开始请求")
        
        if let keyViewController = UIApplication.shared.keyWindow?.rootViewController {
            
            HUD.mode = MBProgressHUDMode.indeterminate
            HUD.bezelView.color = UIColor.lightGray
            HUD.removeFromSuperViewOnHide = true
            HUD.backgroundView.style = .solidColor 
            HUD = MBProgressHUD.showAdded(to: keyViewController.view, animated: true)
            
        }
    }
    
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        print("结束请求")
        HUD.hide(animated: true, afterDelay: 0)

        guard case Result.failure(_) = result else {
            return
        }
        /// 请求失败
        let errorReason: String = (result.error?.errorDescription)!
        print("请求失败: \(errorReason)")
        /// 没网 "The Internet connection appears to be offline."
        /// 连接不到服务器 "Could not connect to the server."
        var tip = "请求失败!"
        if errorReason.contains("The Internet connection appears to be offline") {
            tip = "网络不给力,请检查您的网络"
        }
        if errorReason.contains("Could not connect to the server") {
            tip = "无法连接服务器"
        }
        /// 使用tip文字 进行提示
    }
}

然后初始化MoyaProvider的时候带上自定义的插件

let bsProvider = MoyaProvider<bsAPI>(requestClosure: requestTimeoutClosure , plugins: [RequestLoadingPlugin(), networkPlugin])

大功告成!!!
然后去简单使用下

bsProvider.request(bsAPI.testNetwork) { (result) in
            switch result{
            case let .success(res):
                print("======\(res)======")
                let responseDict = JSON(res.data)  ///这里是导入了SwiftyJSON
                print(responseDict)
                
            case let .failure(error):
                print("=====\(error)=========")
                
            }
        }

我是把API的定义和Manager分开了,在bsNetworkAPI文件里写测试地址/正式地址,接口名。这样子要找接口,改接口一目了然。

项目截图

bsNetworkAPI文件代码大致如下

import UIKit

#if DEBUG
let Using_URL = "" //测试地址
#else
let Using_URL = "" //正式地址
#endif

/// 登陆
let FUNC_Login = "login"

/// 测试
let FUNC_Test = "test"

bsNetworkManager里的代码就是上文里面拆开讲的那部分,给大家一个合集,这样更方便学习。我就比较喜欢全部的代码都给我,然后我在去自己拆解来看。

import UIKit
import Moya
import Result
import MBProgressHUD
import SwiftyJSON

//API 参数列表
enum bsAPI {
    
    case getRequest
    case Login(phoneNumber: String, password: String)
    case testNetwork
    
}

let networkPlugin = NetworkActivityPlugin { (type) in
    switch type {
    case .began:
        /// 状态栏转圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        
    case .ended:
        /// 状态栏停止转圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }
}

let requestTimeoutClosure = { (endpoint: Endpoint<bsAPI>, done: @escaping MoyaProvider<bsAPI>.RequestResultClosure) in
    
    guard var request = endpoint.urlRequest else { return }
    request.timeoutInterval = 10    //设置请求超时时间
    done(.success(request))
    
}

class RequestLoadingPlugin: PluginType {
    
    var HUD:MBProgressHUD = MBProgressHUD.init()
    
    func willSend(_ request: RequestType, target: TargetType) {
        print("开始请求")
        
        if let keyViewController = UIApplication.shared.keyWindow?.rootViewController {
            
            HUD.mode = MBProgressHUDMode.indeterminate
            HUD.bezelView.color = UIColor.lightGray
            HUD.removeFromSuperViewOnHide = true
            HUD.backgroundView.style = .solidColor
            HUD = MBProgressHUD.showAdded(to: keyViewController.view, animated: true)
            
        }
    }
    
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        print("结束请求")
        HUD.hide(animated: true, afterDelay: 0)

        /// 请求失败
        let errorReason: String = (result.error?.errorDescription)!
        print("请求失败: \(errorReason)")
        /// 没网 "The Internet connection appears to be offline."
        /// 连接不到服务器 "Could not connect to the server."
        var tip = "请求失败!"
        if errorReason.contains("The Internet connection appears to be offline") {
            tip = "网络不给力,请检查您的网络"
        }
        if errorReason.contains("Could not connect to the server") {
            tip = "无法连接服务器"
        }
        /// 使用tip文字 进行提示
    }
}

let bsProvider = MoyaProvider<bsAPI>(requestClosure: requestTimeoutClosure , plugins: [RequestLoadingPlugin(), networkPlugin])

extension bsAPI: TargetType {
    
    //网址
    var baseURL: URL{
        return URL.init(string: Using_URL)!
    }
    
    //内容拼接
    var path:String{
        return FUNC_Test
    }
    
    var method: Moya.Method{
        switch self {
        case .getRequest:
            return .get
        default:
            return .post
        }
    }
    
    var parameters: [String: Any]?{
        
        switch self {
        case .Login(let phoneNumber, let password):
            let words = EncodeAction(dic: ["phoneNumber":phoneNumber, "password":password], key: FUNC_Login.md5())
            return ["func":"", "words":words]
            
        case .testNetwork:
            let words: String = EncodeAction(dic: Dictionary<String, Any>(), key: FUNC_Test.md5())
            return ["words":words]
            
        default:
            return nil
        }
    }
    
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    /// Provides stub data for use in testing.
    var sampleData: Data {
        return "".data(using: .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
    }
}

还有疑问可以去Github看看官方教程或下载官方例子——Moya

如果有对MBProgressHUD的那部分有疑问的,可以去iOS版 - MBProgressHUD设置背景方框为透明

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,472评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • 缘份真是个奇妙的东西,从陌生到熟悉,一切都那么顺其自然水到渠成。就像我们俩注定会有故事,那么相似又那么互补。最喜欢...
    爱吃橙子的周周阅读 212评论 0 1
  • 怀孕初期是在一月份,那个时候虽然严重害喜,却很想吃酸酸的青桔子。那种酸和柠檬的酸涩不一样,入口一咬,顿时酸味溢...
    无梦楼缘缘堂阅读 308评论 0 2
  • 人生就如旅行,有人视自己为粪土,羡慕着别人的既可以朝九晚五,又能够浪迹天涯,而不知自己一直在和精彩擦肩而过。...
    M沐梵阅读 289评论 0 1