在OC开发中网络请求通常都使用
AFNetworking
,在Swift虽然也可以使用,但是推荐使用Swift语法实现的网络请求库Alamofire
。
通常大家会对请求库进行一次或多次封装,方便维护,我也不另外。刚开始参考了很多文章,最终决定使用Alamofire
+Moya
+SwiftyJSON
实现网络请求工具类和API
管理类
本篇末尾介绍另一种通过链式封装Alamofire
的实现方式
GitHub Demo 地址
Alamofire
是一个使用Swift开发的网络请求库,其开发团队是AFNetworking
的原团队。它语法简洁,采用链式编程的思想,使用起来是相当的舒服。本质是基于NSURLSession进行封装。
Moya
是对Alamofire
的再次封装。
SwiftyJSON
是数据解析
pod 'Alamofire', '4.9.1'
pod 'Moya', '13.0.1'
pod 'SwiftyJSON', '5.0.1'
首先需要创建3个Swift文件:
一个是网络请求工具类:JhHttpTool.swift
一个Moya配置文件:MoyaConfig.swift
一个API管理文件:APIManager.swift
接下来直接上代码
//
// JhHttpTool.swift
// JhSwiftDemo
//
// Created by Jh on 2021/12/28.
// 网络请求工具类:Alamofire + Moya + SwiftyJSON
import Foundation
import Moya
import SwiftyJSON
public class JhHttpTool {
/// 使用Moya的请求封装
///
/// - Parameters:
/// - target: 请求API,TargetType里的枚举值
/// - success: 成功的回调
/// - error: 连接服务器成功但是数据获取失败
/// - failure: 连接服务器失败
public class func request<T: TargetType>(_ target: T, success: @escaping((Any) -> Void), failure: ((Int?, String) ->Void)?) {
let provider = MoyaProvider<T>(plugins: [
RequestHandlingPlugin(),
// networkLoggerPlugin
])
provider.request(target) { result in
switch result {
case let .success(response):
// let json = try? response.mapString()
// let responseObject = try? response.mapJSON()
// JhLog( responseObject ?? "" );
do {
// *********** 这里可以统一处理错误码,弹出提示信息 ***********
let resObject = try? response.mapJSON()
let responseObject = JSON(resObject ?? "")
let code = responseObject["code"].intValue
let msg = String(describing: responseObject["msg"])
switch (code) {
case 200 :
// 数据返回正确
success(responseObject)
case 401:
// 请重新登录
failure!(code,msg)
alertLogin(msg)
default:
// 其他错误
failureHandle(failure: failure, stateCode: code, message: msg)
}
}
case let .failure(error):
let statusCode = error.response?.statusCode ?? 1000
let message = "请求出错,错误码:" + String(statusCode)
JhAllLog(message)
failureHandle(failure: failure, stateCode: statusCode, message: error.errorDescription ?? message)
}
}
// 错误处理 - 弹出错误信息
func failureHandle(failure: ((Int?, String) ->Void)? , stateCode: Int?, message: String) {
Alert.show(type: .error, text: message)
failure?(stateCode ,message)
}
// 登录弹窗 - 弹出是否需要登录的窗口
func alertLogin(_ title: String?) {
// TODO: 跳转到登录页的操作:
}
}
// MARK: - 打印日志
// static let networkLoggerPlugin = NetworkLoggerPlugin(verbose: true, cURL: true, requestDataFormatter: { data -> String in
// return String(data: data, encoding: .utf8) ?? ""
// }) { data -> (Data) in
// do {
// let dataAsJSON = try JSONSerialization.jsonObject(with: data)
// let prettyData = try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
// return prettyData
// } catch {
// return data
// }
// }
}
//
// JhHttpRequest.swift
// JhSwiftDemo
//
// Created by Jh on 2022/2/10.
// Moya 配置文件
import Foundation
import Moya
// MARK: - 1、2需要根据项目进行更改
/**
1、配置TargetType协议可以一次性处理的参数
- Todo: 根据自己的需要更改,不能统一处理的移除下面的代码,并在APIManager中实现
**/
public extension TargetType {
// // 放到APIManager中了
// var baseURL: URL {
// return URL(string: "http://xxxxx")!
// }
var headers: [String : String]? {
return nil
}
var sampleData: Data {
return "{}".data(using: String.Encoding.utf8)!
}
}
/**
2、公共参数
- Todo: 配置公共参数,例如所有接口都需要传token,version,time等,就可以在这里统一处理
- Note: 接口传参时可以覆盖公共参数。下面的代码只需要更改 【private var commonParams: [String: Any]?】
**/
extension URLRequest {
//TODO:处理公共参数
private var commonParams: [String: Any]? {
//所有接口的公共参数添加在这里:
let header = [
"Content-Type": "application/x-www-form-urlencoded",
"systemType": "iOS",
"version": "1.0.0",
"token": getToken(),
]
return header
// 如果不需要传空
// return nil
}
private func getToken() -> String {
return "1"
}
}
//下面的代码不更改
class RequestHandlingPlugin: PluginType {
public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var mutateableRequest = request
return mutateableRequest.appendCommonParams();
}
}
//下面的代码不更改
extension URLRequest {
mutating func appendCommonParams() -> URLRequest {
let request = try? encoded(parameters: commonParams, parameterEncoding: URLEncoding(destination: .queryString))
assert(request != nil, "append common params failed, please check common params value")
return request!
}
func encoded(parameters: [String: Any]?, parameterEncoding: ParameterEncoding) throws -> URLRequest {
do {
return try parameterEncoding.encode(self, with: parameters)
} catch {
throw MoyaError.parameterEncoding(error)
}
}
}
//
// APIManager.swift
// JhSwiftDemo
//
// Created by Jh on 2021/12/28.
// 接口管理
import Foundation
import Moya
/// 基础域名
let kBaseURL = "https://www.fastmock.site/mock/1010b262a743f0b06c565c7a31ee9739/root"
enum API {
case login(params:Dictionary<String,Any>)
// 获取分页数据
case getPageList(_ page:Int)
// 获取分组分页数据
case getGroupPageList(page:Int)
// 获取联系人数据
case getContact
// 获取微信运行排行榜
case getWxMotionTops
// 获取固定数据
case getSimpleArrDic
///其他接口...
case other1(p1: String, p2: Int, p3: String, p4: String)
case other2
}
// MARK: - 补全【MoyaConfig 3:配置TargetType协议可以一次性处理的参数】中没有处理的参数
extension API: TargetType {
//0. 基础域名,整个项目只用一个,可以写在MoyaConfig中
var baseURL: URL {
switch self {
case .login:
return URL(string:kBaseURL)!
default:
return URL(string:kBaseURL)!
}
}
//1. 每个接口的相对路径
//请求时的绝对路径是 baseURL + path
var path: String {
switch self {
case .login:
return "/login"
case .getPageList:
return "/mock/pages"
case .getGroupPageList:
return "/mock/groupPages"
case .getContact:
return "/mock/contacts"
case .getWxMotionTops:
return "/mock/wxMotionTops"
case .getSimpleArrDic:
return "/getSimpleArrDic"
case let .other1(p1, p2, _, _):
return "/list?id=\(p1)&page=\(p2)"
case .other2:
return ""
}
}
//2. 每个接口要使用的请求方式
var method: Moya.Method {
switch self {
case
.getPageList,
.getGroupPageList,
.other1,
.other2:
return .get
case
.getContact,
.getWxMotionTops,
.getSimpleArrDic,
.login:
return .post
}
}
//3. Task是一个枚举值,根据后台需要的数据,选择不同的http task。
var task: Task {
var params: [String: Any] = [:]
switch self {
case .login:
return .requestPlain
case let .getPageList(page):
params["page"] = page
params["limit"] = 15
params["maxCount"] = 100
case let .other1(_, _, p3, p4):
params["p3"] = p3
params["p4"] = p4
default:
//不需要传参数的接口走这里
return .requestPlain
}
return .requestParameters(parameters: params, encoding: URLEncoding.default)
}
}
// Alamofire + Moya + SwiftyJSON
JhHttpTool.request(API.getPageList(1)) {[weak self] json in
self?.mTextView.text = String(describing: JSON(json))
JhAllLog(JSON(json))
} failure: {code, msg in
JhLog("code : \(code!)")
JhLog("message : \(msg)")
}
Alamofire
链式封装,API可以直接通过一个文件进行管理,里面只放url路径
//
// JhRequest.swift
// JhSwiftDemo
//
// Created by Jh on 2021/12/28.
// 链式网络请求工具类:Alamofire + SwiftyJSON
import Foundation
import Alamofire
import SwiftyJSON
enum HttpRequestType {
case get
case post
}
public let JhRequest = NetworkKit.shared
// Networkkit属性设置
public class NetworkKit {
public static let shared = NetworkKit()
typealias SuccessHandlerType = ((JSON) -> Void)
typealias FailureHandlerType = ((Int?, String) ->Void)
private var requestType: HttpRequestType = .post//请求类型
private var url: String? // URL
private var params: [String: Any]? // 参数
private var success: SuccessHandlerType? // 成功的回调
private var failure: FailureHandlerType? // 失败的回调
private var httpRequest: Request?
}
// NetworkKit属性的设置
extension NetworkKit{
/// 设置url
func url(_ url: String?) -> Self {
self.url = url
return self
}
/// 设置post/get 默认post
func requestType(_ type:HttpRequestType) -> Self {
self.requestType = type
return self
}
/// 设置参数
func params(_ params: [String: Any]?) -> Self {
self.params = params
return self
}
/// 成功的回调
func success(_ handler: @escaping SuccessHandlerType) -> Self {
self.success = handler
return self
}
///失败的回调
func failure(handler: @escaping FailureHandlerType) -> Self {
self.failure = handler
return self
}
}
// NetworkKit请求相关
extension NetworkKit{
/// 发起请求 设置好相关参数后再调用
func request() -> Void {
var dataRequest: DataRequest? // alamofire请求后的返回值
// 发起请求
if let URLString = url {
ProgressHUD.show()
let method = requestType == .get ? HTTPMethod.get : HTTPMethod.post
dataRequest = Alamofire.request(URLString, method: method, parameters: params)
httpRequest = dataRequest
}
dataRequest?.responseJSON {
(response) in
ProgressHUD.hide()
switch response.result {
case let .success(response):
do {
// *********** 这里可以统一处理错误码,弹出提示信息 ***********
let responseObject = JSON(response)
let code = responseObject["code"].intValue
let msg = String(describing: responseObject["msg"])
switch (code) {
case 200 :
// 数据返回正确
self.success?(responseObject)
case 401:
// 请重新登录
self.failure?(code,msg)
alertLogin(msg)
default:
// 其他错误
failureHandle(failure: self.failure, stateCode: code, message: msg)
}
}
case let .failure(error):
failureHandle(failure: self.failure, stateCode: nil, message: error.localizedDescription)
}
}
// 错误处理 - 弹出错误信息
func failureHandle(failure: FailureHandlerType? , stateCode: Int?, message: String) {
Alert.show(type: .error, text: message)
failure?(stateCode ,message)
}
// 登录弹窗 - 弹出是否需要登录的窗口
func alertLogin(_ title: String?) {
// TODO: 跳转到登录页的操作:
}
}
// 取消请求
func cancel() {
httpRequest?.cancel()
}
}
// 链式网络请求:Alamofire + SwiftyJSON
let url = kBaseURL + "/getSimpleArrDic"
JhRequest.url(url).params([:]).requestType(.post).success { res in
JhLog(" ========链式网络请求======== ")
JhAllLog(res)
JhAllLog(res["code"])
}.failure { code, msg in
JhLog("code : \(code!)")
JhLog("message : \(msg)")
}.request()