iOS swift moya的使用,二次封装,添加loading以及数据缓存

Moya

使用 Moya 作为项目中的网络层有段时间了,一般在Swift项目中,我们用Alamofire来做网络库.而Moya在Alamofire的基础上又封装了一层,方便我们的使用。但是在实际的项目中我们又会对Moya进行封装方便项目的使用,下面具体说说实际项目中Moya是怎么使用的。
先附上项目地址: 项目地址

Moya 优势
1. 编译时检查正确的API端点访问.
2. 使你定义不同端点枚举值对应相应的用途更加明晰.
3. 提高测试地位从而使单元测试更加容易.
本文主要介绍以下内容
1. Moya的用法
2. 设置请求头部信息
3. 设置超时时间
4. 自定义插件
5. 统一添加 loading
6. 网络层缓存
7. 打印请求参数及其返回数据
8. 自签名证书
一. Moya的具体用法以及代码实现
// 首页接口
let HomeProvider = MoyaProvider<HomeGoodsAPI>()


enum HomeGoodsAPI {
    case homeGoodsList // 首页上面列表
    case homePageBelowConten // 首页下面列表
    case goodDetail(goodId: String) // 商品详情
    case goodCategory //商品类别信息
    case categoryGoodsList(categoryId: String,CategorySubId: String,page: Int) //商品列表

}


// 遵循 TargetType 代理 实现方法
extension HomeGoodsAPI: TargetType {
    //服务器地址
    public var baseURL: URL {
         return URL(string: "https://wxmini.baixingliangfan.cn/baixing/")!
    }

    var path: String {
        switch self {
            case .homeGoodsList: return "wxmini/homePageContent"
            case .homePageBelowConten: return "wxmini/homePageBelowConten"
            case .goodDetail: return "wxmini/getGoodDetailById"
            case .goodCategory: return "wxmini/getCategory"
            case .categoryGoodsList: return "wxmini/getMallGoods"

        }
    }

    var method: Moya.Method {
        return .post
    }
    
    var task: Task {
        var parmeters:[String:Any] = [:]
        switch self {
        case .homeGoodsList:
            parmeters = ["lon":"116.47118377685547","lat":"39.91233444213867"]
        case .homePageBelowConten:
            parmeters = ["page":"1"]
        case .goodDetail(let goodId):
            parmeters = ["goodId":goodId]
        case .goodCategory:
            parmeters = ["":""]
        case .categoryGoodsList(let categoryId,let CategorySubId,let page):
            parmeters = ["categoryId":categoryId,"categorySubId":CategorySubId,"page":page]
        }
        
        return .requestParameters(parameters: parmeters, encoding: URLEncoding.default)
    }
    
    var sampleData: Data {
        
        return "".data(using: String.Encoding.utf8)!
        
    }
    var headers: [String : String]? { return nil }

}

1. 初始化 HomeProvider 对象,将创建的枚举 HomeGoodsAPI 传入。
2. 在HomeGoodsAPI 中设置接口名,需要传入参数的在枚举中设置参数值。
3. 遵循 TargetType 代理 实现方法,设置 baseURL 即项目主域名。
4. 设置path 即 接口的方法名。
5. 设置请求参数 Task 。

在VC中调用 result 即 网络返回
        HomeProvider.request(.goodCategory) { (result) in
            switch result {
            case .success(_):
            
                break
            case .failure(_):

                break
            @unknown default:

                break
            }
        }
在OC中大家习惯的将项目中的网络请求统一封装一个公共类,在里面设置超时时间,设置请求头,设置加载控件,设置网络缓存,设置返回到VC中的数据格式,JSON或者data 等等,新建一个NetworkManager 类,用来专门处理网络请求。
二.设置 请求头 公共参数
// 网络请求的基本设置,这里可以拿到是具体的哪个网络请求,可以在这里做一些设置
private let myEndpointClosure = { (target: API) -> Endpoint in
    /// 这里把endpoint重新构造一遍主要为了解决网络请求地址里面含有? 时无法解析的bug
    let url = target.baseURL.absoluteString + target.path
    var task = target.task
    /*
     如果需要在每个请求中都添加类似token参数的参数请取消注释下面代码
     */
//    let additionalParameters = ["token":"888888"]
//    let defaultEncoding = URLEncoding.default
//    switch target.task {
//        ///在你需要添加的请求方式中做修改就行,不用的case 可以删掉。。
//    case .requestPlain:
//        task = .requestParameters(parameters: additionalParameters, encoding: defaultEncoding)
//    case .requestParameters(var parameters, let encoding):
//        additionalParameters.forEach { parameters[$0.key] = $0.value }
//        task = .requestParameters(parameters: parameters, encoding: encoding)
//    default:
//        break
//    }
    /*
     如果需要在每个请求中都添加类似token参数的参数请取消注释上面代码
     */

    var endpoint = Endpoint(
        url: url,
        sampleResponseClosure: { .networkResponse(200, target.sampleData) },
        method: target.method,
        task: task,
        httpHeaderFields: target.headers
    )
    requestTimeOut = 30 // 每次请求都会调用endpointClosure 到这里设置超时时长 也可单独每个接口设置
    switch target {
    case .homeGoodsList:
        return endpoint
    case .homePageBelowConten:
        requestTimeOut = 5
        return endpoint
    default:
        return endpoint
    }
}
三. 设置请求时长,打印请求参数和数据返回
/// 网络请求的设置
private let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
    do {
        var request = try endpoint.urlRequest()
        // 设置请求时长
        request.timeoutInterval = requestTimeOut
        // 打印请求参数
        if let requestData = request.httpBody {
            print("\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "发送参数" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
            parmeterStr = String(data: request.httpBody!, encoding: String.Encoding.utf8)!
            
        // [URL absoluteString];
            
        } else {
            print("\(request.url!)" + "\(String(describing: request.httpMethod))")
        }
        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error, nil)))
    }
}
四.设置加载loading 使用 NetworkActivityPlugin 插件
// NetworkActivityPlugin插件用来监听网络请求,界面上做相应的展示
private let networkPlugin = NetworkActivityPlugin.init { changeType, _ in
    print("networkPlugin \(changeType)")
    // targetType 是当前请求的基本信息
    switch changeType {
    case .began:
        print("开始请求网络")
        
        SVProgressHUD .setDefaultMaskType(SVProgressHUDMaskType.clear)
        SVProgressHUD .setBackgroundLayerColor(UIColor .blue)
        SVProgressHUD .setDefaultStyle(SVProgressHUDStyle.light)
        SVProgressHUD .setForegroundColor(MainColor)
        SVProgressHUD .setDefaultAnimationType(SVProgressHUDAnimationType.flat)
        SVProgressHUD .show(withStatus: "加载中")
        SVProgressHUD .setMinimumDismissTimeInterval(20.0)
        
    case .ended:
        print("结束")
        SVProgressHUD .dismiss()
    }
}
五. 主网络请求方法封装以及数据缓存
// target HomeProvider 对象
/// isCarch 是否需要缓存,默认false
/// carchID 缓存参数
func NetWorkRequest(_ target: API, isCarch: Bool = false, carchID: NSString = "", completion: @escaping successCallback, failed: failedCallback?, errorResult: errorCallback?) -> Cancellable? {
    
    // 先判断网络是否有链接 没有的话直接返回--代码略
    /*
     if !UIDevice.isNetworkConnect {
         print("提示用户网络似乎出现了问题")
         return nil
     }
     */
    
    /// 缓存代码 设置缓存路径
    let pathcaches = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
    let cachesDir = pathcaches[0]
    
    let mutableSting = target.baseURL.absoluteString + target.path + (carchID as String)
    let lastStr = mutableSting.replacingOccurrences(of: "/", with: "-")
    let disPath = cachesDir + "/" + lastStr + "-.text"
    if isCarch == true {
        DispatchQueue.global().async {
            do {
                /// 获取json字符串
                let str = try String .init(contentsOfFile: disPath, encoding: String.Encoding.utf8)
                DispatchQueue.main.async {
                    /// 字符串转化为data
                    let data = str .data(using: String.Encoding.utf8, allowLossyConversion: true)
                    completion(data! as NSData)
                }
            } catch {
                print(error)
            }
        }
    }
    
    return Provider.request(target) { result in
        // 隐藏hud
        switch result {
        case let .success(response):
            do {
                let jsonData = try JSON(data: response.data)
                print(jsonData)
                
                if isCarch == true {
                    // 缓存
                    let jsonStr = String(data: response.data, encoding: String.Encoding.utf8) ?? ""
                    DispatchQueue.global().async {
                        do {
                            try jsonStr .write(toFile: disPath, atomically: true, encoding: String.Encoding.utf8)
                        } catch {
                            print(error)
                        }
                    }
                }
                /// 这里的completion和failed判断条件依据不同项目来做,为演示demo我把判断条件注释了,直接返回completion。

                completion(response.data as NSData)

                print("flag不为1000 HUD显示后台返回message" + "\(jsonData[RESULT_MESSAGE].stringValue)")

                if jsonData[RESULT_CODE].stringValue == "1000"{
                    completion(response.data as NSData)
                }else{
//                    failed?(String(data: try! response.mapJSON() as! Data, encoding: String.Encoding.utf8)!)
                }

            } catch {}
        case let .failure(error):
            // 网络连接失败,提示用户
            print("网络连接失败\(error)")
            errorResult?()
        }
    }
}
调用
NetWorkRequest(.homePageBelowConten(parameters: ["page":self.page]),isCarch: true,carchID: "page-\(page)" as NSString, completion: { (responseString) -> (Void) in
         // 轮播图数据
        let json = JSON(responseString
        // 数据处理刷新UI
        }, failed: { (failedResutl) -> (Void) in
            print("服务器返回code不为0000啦~\(failedResutl)")
        }, errorResult: { () -> (Void) in
            print("网络异常")
        })

首先说一下这个函数的几个参数:
1. _target HomeProvider 对象,就是将Provider传入。
2.isCarch 是否需要缓存,默认false,因为一个App一般情况下并不需要全部页面缓存,只是首页或者个别页面需要缓存来提高用户体验,当不需要缓存时这个参数不用处理,需要时传true就ok
carchID 缓存参数 这个参数主要是为了区分相同的接口不同的参数去做缓存时存在不同的路径下面,从上面的方法可以看到缓存具体的思路就是 以 target.baseURL.absoluteString + target.path 为主路径将数据缓存到沙盒,当网络请求时如果沙盒中有数据开辟线程先将沙盒中的缓存取出来,回调到VC,展示UI。等网络请求结束再将网络端的数据回调,这样的思路。那么在缓存时就要主要,同样的接口如果参数不一样时那么就需要传这个carchID 了。比如此项目中,请求商品详情数据,每个商品的goodId是不一样的,如果有需求说缓存一下商品详情,那个肯定是一个商品一个缓存路径对应着一个缓存文件,所以必须穿一个carchID 。
3.回调:我直接回掉的response.data,当然也可以在网络层将数据转化成model回调给VC这个可以随意修改了

写到这里基本上就可以满足项目的需求了,如有遗漏,还请指教。

代码地址

关于swift的更多知识

请点击 swift文集!

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

推荐阅读更多精彩内容