[Swift]封裝一個網絡工具Moya+HandyJSON

網絡請求是 App 中最經常使用的更能之一,除了 Apple 提供的 URLSession 以外,還有對其進行封裝,功能更增強的的 Alamofire等強大的工具,儘管這樣,咱們依然會在本身的 App 中封裝一套網絡請求工具,以達到作網絡請求時,代碼簡潔高效.html

封裝目的:

作網絡請求的時候儘可能的簡單,只須要少許的代碼便可處理返回的數據git

封裝過程:

1.實現網絡請求單例,提供可修改 baseURL和 get post 請求方法.

enum RequestMethod {
    case post
    case get
}
///請求對象 
struct Requset {
    init(method: RequestMethod = .post, baseURL: String = "", path: String, parameters: [String : Any]?){
        self.method = method
        self.baseURL = baseURL
        self.path = path
        self.parameters = parameters ?? [:]
    }
    var method: RequestMethod
    var baseURL: String
    var path: String
    var parameters: [String: Any]
}
///網絡工具
class NetworkManager {
    static let shared = NetworkManager("")
    init(_ baseURL: String = "") {
        self.baseUrl = baseURL
    }
    var baseUrl = ""
    func post(path: String,params: [String: Any]?,result: @escaping ((Result<Data,Error>)->())){
        let request = Requset(baseURL:baseUrl, path: path, parameters: params)
        let target = Target(request: request)
        self.request(target: target, result: result)
    }
    func get(path: String,params: [String: Any]?,result: @escaping ((Result<Data,Error>)->())){
        let request = Requset(baseURL:baseUrl, path: path, parameters: params)
        let target = Target(request: request)
        self.request(target: target, result: result)
    }
    private func request(target: Target,result: @escaping ((Result<Data,Error>)->())) {
        MoyaProvider<Target>().request(target) { (res) in
            switch res {
            case .success(let a):
                result(.success(a.data))
            case .failure:
                result(.failure(res.error!))
            }
        }
    }
}
複製代碼

如今已經能夠作網絡請求了,好比:把百度首頁數據請求下來github

NetworkManager("https://www.baidu.com").get(path: "", params: nil) { (res) in
    let data = try! res.get()
    print(String(data: data, encoding: .utf8))
}
複製代碼

Optional("\r\n\r\n\r\n\r\n <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">\r\n <meta http-equiv="content-type" content="text/html;charset=utf-8">\r\n <meta content="always" name="referrer">\r\n <script src="ss1.bdstatic.com/5eN1bjq8AAU…">\r\n 頁面不存在_百度搜索\r\n ....chrome

2. 二次封裝

僅僅這樣封裝很明顯不可以達到精簡的目的,這樣其實和直接使用 Alamofire 沒啥區別.json

通常後臺返回的數據都有固定的格式,好比:api

{
    "msg": "請求成功",
    "code": 1001,
    "data": {...}
}
或者:
{
    "msg": "請求成功",
    "code": 1001,
    "data": [...]
}
複製代碼

將其轉換成對應的模型:bash

public struct DataResponse<T> {
    public init (){}
    public var code: Int = -1
    public var msg: String = ""
    public var data: T?
}
public struct ListResponse<T> {
    public init (){}
    public var code: Int = -1
    public var msg: String = ""
    public var data: [T] = []
}
複製代碼

咱們實現一個 Protocol 繼承自 HandyJSON (HandyJSON自己也是協議),而後是咱們的 Response 遵照這個協議.網絡

public protocol RequestProtocol: HandyJSON {
    static func request(api: API, params: [String: Any]?, result: ((ResponseResult<Self>)->())?)
}

public extension RequestProtocol where Self: HandyJSON {
    static func request(api: API, params: [String: Any]?, result: ((ResponseResult<Self>)->())?) {
        let completionHandle: ((Result<Data,Error>)->()) = { res in
            switch res {
            case .success(let data):
                let jsonStr = String(data: data, encoding: .utf8)
                #if DEBUG
                print("url: \(api.path)")
                print("response:")
                print(jsonStr ?? "")
                #endif
                ///不是 json 數據,拋出 json 解析失敗錯誤
                guard let jsonObj = self.self.deserialize(from: jsonStr) else {
                    result?(.failure(.deserializeFailed))
                    return
                }
                result?(.success(jsonObj))
            case .failure(_):
                ///處理錯誤 拋出去
                result?(.failure(.requestFailed))
            }
        }
        if api.method == .post {
            NetworkManager.shared.post(path: api.path, params: params, result: completionHandle)
        }else{
            NetworkManager.shared.get(path: api.path, params: params, result: completionHandle)
        }
    }
}
複製代碼

而後久能夠以下優雅的作請求:ide

NetworkManager.shared.baseUrl = "https://api.douban.com"
BookResponse.request(api: .getBookDetail, params: nil) { (res) in
    guard res.isSuccess else { return }
    print(res.value?.toJSON())
}
複製代碼

{"msg":"invalid_apikey","code":104,"request":"POST /v2/book/1220562"}工具

API 是這麼樣的結構體

public struct API {
    var path: String
    var method: RequestMethod
    init(path: String, method: RequestMethod = .post) {
        self.path = path
        self.method = method
    }
}
///能夠經過這種方式 減小硬編碼可能會帶來的錯誤
extension API {
    static let getBookDetail = API(path: "/v2/book/1220562")
}
複製代碼

Demo地址

相關文章
相關標籤/搜索