前言
上一篇文章我们已经获取到贝乐虎启蒙App的一些图片资源,这一篇我们就开始封装一下网络请求。在封装之前我们要对虎启蒙App进行抓包,这里我们使用Charles`,这个相信大家都不陌生吧,如果有不了解的可以看下这篇文章对于Charles的使用
使用Charles抓包
下面是首页的UI,
我们先来抓个首页的数据试试,打开app后请求到不少的数据,但是没有首页顶部的推荐、唱儿歌、看动、读绘本、听故事,这五个应该是本地写死的
接下来我们分别切换推荐、唱儿歌、看动、读绘本、听故事来获取数据
现在首页的数据已经获取到了,接下来我们就开始封装网络请求
网络请求封装
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
}
请求是可以的,请求结果这里就不展示了。
虽然说这个网络请求能够使用,但是请求参数和数据返回这块还不够友好,接下来我们对请求参数优化下。
因为每个请求接口都有
url、method、parameters、encoding、headers等,所以还是用协议来处理吧
网络请求入参处理
1、新增一个TargetType协议,因为每个url里面有公共的部分,所以url拆成baseURL和path
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
}
在TargetType的extension里面新增一个request()方法,在该方法里面调用NetworkManager的request()方法
extension TargetType {
func request() -> NetworkManager {
return NetworkManager.share.request(self)
}
}
3、新建一个Network,接口请求入口,以后接口都按照模块分
struct Network {}
现在就以首页的接口看下怎么掉用吧,在Network的extension里面新增一个枚举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
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
}