阅读 1304

使用Swift开发一个贝乐虎启蒙App - 网络请求封装

前言

上一篇文章我们已经获取到贝乐虎启蒙App的一些图片资源,这一篇我们就开始封装一下网络请求。在封装之前我们要对虎启蒙App进行抓包,这里我们使用Charles`,这个相信大家都不陌生吧,如果有不了解的可以看下这篇文章对于Charles的使用

使用Charles抓包

下面是首页的UI,

WechatIMG159.jpeg

我们先来抓个首页的数据试试,打开app后请求到不少的数据,但是没有首页顶部的推荐、唱儿歌、看动、读绘本、听故事,这五个应该是本地写死的

截屏2021-08-03 10.15.16.png

接下来我们分别切换推荐、唱儿歌、看动、读绘本、听故事来获取数据

截屏2021-08-03 10.25.40.png

现在首页的数据已经获取到了,接下来我们就开始封装网络请求

网络请求封装

Swift中网络请求我们使用 Alamofire,然后对其进一步封装。

首先我们使用CocoaPods导入Alamofire库,然后新建一个NetworkManager网络管理类,引入Alamofire

NetworkManager是一个单例,里面增加一个dataRequest字典属性,其key是请求接口的URL参数组成的,value是一个请求任务(DataRequest),为什么要增加这个属性呢?是因为准备用链式调用,方便后面取出响应数据

import Alamofire

class NetworkManager: NSObject {

    static let share = NetworkManager()
    private var dataRequest = [String: DataRequest]()
    
    public func request(_ url: String,
                        method: HTTPMethod = .post,
                        parameters: Parameters? = nil,
                        encoding: ParameterEncoding = JSONEncoding.default,
                        headers: HTTPHeaders? = nil) -> NetworkManager {
                        
        let key = requestKey(url, parameters)
        let request = AF.request(url,
                                 method: method,
                                 parameters: parameters,
                                 encoding: encoding,
                                 headers: headers)
        dataRequest[key] = request
        return self

    }
    
    public func responseData(completion: @escaping (Data) -> Void,
                             failure: @escaping (AFError) -> Void) {
        dataRequest.forEach { key, request in
            dataRequest.removeValue(forKey: key)
            request.responseData { response in
                switch response.result {
                case let .success(data):
                    completion(data)
                case let .failure(error):
                    failure(error)
                }
            }
        }
    }
}
复制代码

现在已经初步封装完成,先看看能不能请求数据

NetworkManager.share
    .request("https://vd.ubestkid.com/api/v1/feature/qmtab_tj3.json", parameters: parameters)
    .responseData { data in
        debugPrint(data)
     } failure: { error in
}
复制代码

请求是可以的,请求结果这里就不展示了。

虽然说这个网络请求能够使用,但是请求参数和数据返回这块还不够友好,接下来我们对请求参数优化下。

因为每个请求接口都有urlmethodparametersencodingheaders等,所以还是用协议来处理吧

网络请求入参处理

1、新增一个TargetType协议,因为每个url里面有公共的部分,所以url拆成baseURLpath

public protocol TargetType {
    var baseURL: String { get }
    var path: String { get }
    var method: HTTPMethod { get }
    var parameters: [String: Any] { get }
    var encoding: ParameterEncoding { get }
    var headers: HTTPHeaders? { get }
}
复制代码

然后给TargetType来个extension,给所有属性一个默认值

extension TargetType {

    var baseURL: String {
        return "https://vd.ubestkid.com/"

    }
    
    var path: String {
        return ""
    }
    
    var method: HTTPMethod {
        return .post

    }
    
    var parameters: [String: Any] {

        return [:]

    }
    
    var encoding: ParameterEncoding {
        return JSONEncoding.default

    }
    
    var headers: HTTPHeaders? {
        return nil
    }
}
复制代码

2、回到NetworkManager里面,修改request方法入参

public func request(_ target: TargetType) -> NetworkManager {

        let url = target.baseURL + target.path
        let key = requestKey(url, target.parameters)
        let request = AF.request(url,
                                 method: target.method,
                                 parameters: target.parameters,
                                 encoding: target.encoding,
                                 headers: target.headers)

        dataRequest[key] = request
        
        return self
    }
复制代码

TargetTypeextension里面新增一个request()方法,在该方法里面调用NetworkManagerrequest()方法

extension TargetType {

    func request() -> NetworkManager {
        return NetworkManager.share.request(self)
    }
}
复制代码

3、新建一个Network,接口请求入口,以后接口都按照模块分

struct Network {}
复制代码

现在就以首页的接口看下怎么掉用吧,在Networkextension里面新增一个枚举Home,以后首页的接口都放在Home里面,Home里面加个list,给个path参数,因为推荐、唱儿歌、看动、读绘本、听故事接口就是qmtab_后面不一样,而且入参都是一样的

推荐: .../api/v1/feature/qmtab_tj3.json
唱儿歌: .../api/v1/feature/qmtab_eg3.json
看动: .../api/v1/feature/qmtab_dh3.json
读绘本: .../api/v1/feature/qmtab_hb3.json
听故事: .../api/v1/feature/qmtab_gs3.json

extension Network {
    enum Home {
        case list(path: String)
    }
}

extension Network.Home: TargetType {

    var path: String {
        switch self {
        case let .list(path):
            return "api/v1/feature/qmtab_\(path).json"
        }
    }
    
    var parameters: [String: Any] {
        switch self {
        case .list:
            return ["mac": "",
                    "exp10": 60,
                    "exp2": 1,
                    "ua": "",
                    "devicetype": 1,
                    "srcApp": "com.ubestkid.collection",
                    "carrier": "46002",
                    "svip_status": 2,
                    "impsize": 1,
                    "exp7": 55,
                    "exp3": 73,
                    "version": "4.0",
                    "make": "apple",
                    "bannersafe": 0,
                    "oaid": "",
                    "sh": 2208,
                    "network": 1,
                    "vps": 10,
                    "sw": 1242,
                    "cpId": "blh",
                    "splashsafe": 0,
                    "channel": "c2",
                    "exp8": 55,
                    "pkg": "com.ubestkid.collection",
                    "exp4": 34,
                    "model": "iPhone8,2",
                    "osv": "14.6",
                    "idfa": "",
                    "ppi": 401,
                    "apiVersion": "1.1.0",
                    "exp9": 37,
                    "os": 1,
                    "androidid": "",
                    "exp5": 39,
                    "ak": "8f75a52eadde46239f2227ba64eab72b",
                    "exp1": 58,
                    "egvip_status": 2,
                    "age": "1",
                    "appver": "3.8.3",
                    "installtime": 1625143625930,
                    "res_type": 0,
                    "ip": "",
                    "imei": "",
                    "userId": "",
                    "exp6": 4]
        }
    }
}
复制代码

然后掉用就如下面:

Network.Home
    .list(path: "tj3")
    .request()
    .responseData { data in
            
} failure: { error in
        
}
复制代码

数据返回处理

现在是可以正常请求了,但是怎么处理返回的数据呢?这里我们使用苹果地带的Codable协议,推荐一个CleanJSON库来转成model,根据首页接口返回的数据,我们建立一个Response基类,遵守Codable

截屏2021-08-03 14.47.22.png

struct Response<T: Codable>: Codable {
    let errorCode: Int
    let errorMessage: String
    let result: T
    
    var success: Bool {
        return errorCode == 0
    }

    var appError: AppError {
        return AppError(code: errorCode, errorMessage: errorMessage)
    }
}
复制代码

回到NetworkManager里面修改responseData()方法

public func responseData<T: Codable>(_ type: T.Type,
                                       completion: @escaping (Response<T>) -> Void,
                                        failure: @escaping (AppError) -> Void) {
        dataRequest.forEach { key, request in
            dataRequest.removeValue(forKey: key)
            request.responseData { response in
                switch response.result {
                case let .success(data):
                    if let responseData = try? CleanJSONDecoder().decode(Response<T>.self, from: data) {
                        if !responseData.success {
                            failure(responseData.appError)
                            return
                        }
                        completion(responseData)
                    } else {
                        failure(AppError(code: 0000, errorMessage: "数据解析失败"))
                    }
                case let .failure(error):
                    failure(error.appError)
                }
           }
      }
 }
复制代码

现在整个网络请求算是封装完成了,最后看下使用吧

Network.Home
    .list(path: "tj3")
    .request()
    .responseData(RecModel.self) { model in
        debugPrint(model.result)
} failure: { error in
        
}
复制代码

项目地址

文章分类
iOS
文章标签