Moya + Codable + 泛型 构造一套网络请求

背景

目前公司大部分网络使用的是socket类型的链接, 所以几乎没有什么使用Moya + Model的强需求, 我也是半路加入项目, 就在原有基础和战友们在继续, 最近公司决定把部分功能拆分一个新的App, 我骚动的心决定构造一个网络请求的小工具方便自己使用, 以下为见解和记录.

思路

思路: 希望使用: 泛型 + Moya + Codable
目标, 最终类似于如下

import WolfNet

let _ = WolfNetwork.request(type: WolfApi.wolfGet, completion: { (user: User?, msg, code) in
print(user?.name)
print(user?.age)
}) { (error) in
}

WolfApi.wolfGet是Moya的请求类型
PriceModel是WolfMapper(Codable)协议的Model

环境配置

Xcode9+, iOS9+, Swift4.0+(支持Codable)
Carthage/Pods 配置Moya, Alamofire环境

开撸

根据我司服务器返回规则来创建, 大家可以根据情况.

// 服务器数据类似于如下, data里面是字典/数组等 
{
     "data" : { },
     "code" : 1000,
     "msg" : "success"
 }
目标是构造一个标题说明的网络请求
  1. 创建自定义Model需要的协议, 预留备用方法func didInit()
  2. 基于Codable对 Data <-> Model 相互转换
  3. 创建外部请求的API
  4. 其他辅助类创建
  5. 请求Demo
1. 创建自定义协议支持Codable
import Foundation
import Moya
import Alamofire
 // 创建自定义model遵循WolfMapper协议即可
public protocol WolfMapper: Codable {
    mutating func didInit()
}
2.基于Codable对 Data <-> Model 相互转换
/// 用以转换 data -> model / model -> data
public class WolfModel {

/// data -> model
///
/// - Parameter data: 请求返回的data
/// - Returns: 返回<T: WolfMapper>的model
  public static func model<T: WolfMapper>(_ data: Data?) -> T? {
    if let data = data {
        do {
            return try JSONDecoder.init().decode(T.self, from: data)
        } catch {
            // 此问题一般表示model属性的类型不匹配, 比如 age: String?, 可能服务器的参数为Int
            debugPrint("key的类型不匹配" + error.localizedDescription)
            return nil
        }
    } else {
        debugPrint("WolfModel传入data为nil")
        return nil
    }
  }

/// model -> data
///
/// - Parameter model: 遵守Encodable/WolfMapper的Model
/// - Returns: 转换成功的data
  public static func data<T: Encodable>(_ model: T?) -> Data? {
    if let model = model {
        do {
            return try JSONEncoder.init().encode(model)
        } catch {
            debugPrint("将\(model.self)转成jsondata失败 \(error.localizedDescription)")
            return nil
        }
    } else {
        debugPrint("传入\(String(describing: model))为nil")
        return nil
    }
  }
}
3. 创建外部请求的API
/// 对外的网络请求API
public class Wolf {

/// 返回一个Model的网络请求
///
/// - Parameters:
///   - type:       Moya.type
///   - progress:   进度
///   - completion: 完成回调
///   - failure:    失败回调
/// - Returns:      Cancellable
  public class func request<T: WolfMapper, R: TargetType>(type: R, progress: ProgressBlock? = nil, completion: ((T?, String?, Int?) -> ())?, failure: ((MoyaError?) -> ())?) -> Cancellable {

    let provider = self.createProvider(type)
    return provider.request(type, progress: progress, completion: { (event) in
        switch event {
        case let .success(response):
            WolfTransformModel.objectFromJSON(response.data, completion)
        case let .failure(error):
            debugPrint(error.localizedDescription)
            failure?(error)
        }
    })
  }

/// 返回一个[Model]的网络请求
///
/// - Parameters:
///   - type:       Moya.type
///   - progress:   进度
///   - completion: 完成回调
///   - failure:    失败回调
/// - Returns:      Cancellable
  public class func requestList<T: WolfMapper, R: TargetType>(type: R, progress: ProgressBlock? = nil, completion: (([T]?, String?, Int?) -> Void)?, failure: ((MoyaError?) -> ())?) -> Cancellable {
    let provider = self.createProvider(type)
    return provider.request(type, progress: progress, completion: { (event) in
        switch event {
        case let .success(response):
            WolfTransformModel.listFromJSON(response.data, completion)
         case let .failure(error):
            debugPrint(error.localizedDescription)
            failure?(error)
        }
    })
  }
}
4. 其他辅助类创建
/// 拼凑一下Moya的Provider
fileprivate class func createProvider<T: TargetType>(_ target: T) -> MoyaProvider<T> {
    
    let endpointClosure = { (target: T) -> Endpoint in
        let url = target.baseURL.appendingPathComponent(target.path).absoluteString
        let endpoint: Endpoint = Endpoint(url: url, sampleResponseClosure: { () -> EndpointSampleResponse in
            return .networkResponse(200, target.sampleData)
        }, method: target.method, task: target.task, httpHeaderFields: target.headers)
        return endpoint.adding(newHTTPHeaderFields: wolf.header)
    }
    if wolf.isDebug {
        return MoyaProvider<T>(endpointClosure: endpointClosure, manager: wolf.sessionManager, plugins: [NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter)])
    } else {
        return MoyaProvider<T>(endpointClosure: endpointClosure, manager: wolf.sessionManager)
    }
}


fileprivate struct WolfTransformModel {

  static func objectFromJSON<T: WolfMapper>(_ Json: Data, _ response: ((T?, String?, Int?) -> Void)?) {
    let tmp: WolfBaseModel<T>? = WolfModel.model(Json)
    response?(tmp?.data, tmp?.msg, tmp?.code)
  }

  static func listFromJSON<T: WolfMapper>(_ Json: Data, _ response: (([T]?, String?, Int?) -> Void)?) {
    let tmp: WolfBaseModels<T>? = WolfModel.model(Json)
    response?(tmp?.data, tmp?.msg, tmp?.code)
  }
}

// Moya插件, Debug使用
private func JSONResponseDataFormatter(_ data: Data) -> Data {
  do {
    let dataAsJSON = try JSONSerialization.jsonObject(with: data)
    let prettyData =  try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
    return prettyData
  } catch {
    return data // fallback to original data if it can't be serialized.
  }
}

public let wolf = WolfParams()
public class WolfParams {
// 请求头
  public var isDebug = false
  public var header = [String: String]()
  public var sessionManager = DefaultAlamofireManager.sharedManager
}


public class DefaultAlamofireManager: Alamofire.SessionManager {

  static let sharedManager: DefaultAlamofireManager = {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
    configuration.timeoutIntervalForRequest = 15
    configuration.timeoutIntervalForResource = 60
    let manager = Manager(configuration: configuration)
    manager.startRequestsImmediately = false
    return DefaultAlamofireManager(configuration: configuration)
  }()
}
5. 请求Demo
struct WolfBaseModel<T: WolfMapper>: WolfMapper {
// 处理 T: WolfMapper的model
func didInit() {
}
var code: Int?
var msg: String?
var data: T?
}

struct WolfBaseModels<T: WolfMapper>: WolfMapper {
// import Foundation

struct WolfBaseModel<T: WolfMapper>: WolfMapper {
// 处理 T: WolfMapper的model
  func didInit() {
  }
  var code: Int?
  var msg: String?
  var data: T?
}

struct WolfBaseModels<T: WolfMapper>: WolfMapper {
//  处理返回[Model]
  func didInit() {
  }
  var code: Int?
  var msg: String?
  var data: [T]?
}

其他调试代码, 以及更加完整demo.
请查看GitHub

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,138评论 25 707
  • 文章摘自Moya官方文档 Targets Moya的使用始于定义一个target——典型的是定义一个符合Targe...
    Jt_Self阅读 16,163评论 0 27
  • 如果沿着故事回溯。 我想我们应该先回到1997年。 因为中村村这个小山村的真正改变始于二十年前。 在此之前,中村已...
    貓太年阅读 300评论 0 0
  • 欣赏自己:敢于背负自己理想的人,才有能力成为别人理想中的人!今天我打了5个马尔代夫旅游冲量的订金,这意味着我要订1...
    芊润阅读 166评论 0 0
  • 你有没有爱上过一座城市 杭州是第一个单纯因为旅行而去的城市 只在那里一天就爱上的城市
    易容阅读 182评论 0 0