GitHub:
https://github.com/Gamin-fzym/DomainManageDemo
之前封装的网络请求方法,最近发发现有些问题。
/// Post请求
/// - Parameters:
/// - url: 请求链接
/// - params: 参数
/// - headers: header信息
/// - completed: 请求成功返回数据
/// - failed: 请求失败返回数据
fileprivate func POST(url: String,
params: [String : Any]?,
headers: HTTPHeaders,
completed: @escaping CompletedBlock,
failed: @escaping FailedBlock) {
sessionManager?.request(url, method: .post, parameters: params, encoding: URLEncoding.default, headers: headers).validate().responseJSON { [weak self] (response) in
guard let data = response.data else {
DispatchQueue.main.async {
failed(.otherError, "服务器出错: \(response.error?.localizedDescription ?? "")")
}
return
}
guard let jsonData = try? JSON(data: data) else {
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
return
}
let status = NetworkStatus(rawValue: jsonData["code"].stringValue)
switch status {
case .success:
completed(jsonData["data"])
case .tokenError:
self?.cancalAllRequest()
AppUserDefaults.shared.exitLoginAction(canToHome: true)
default:
DispatchQueue.main.async {
failed(.otherError, jsonData["msg"].stringValue)
}
}
}
}
后端是PHP,APP端使用的Alamofire框架进行网络请求,问题是服务端无法解析嵌套的JSON字符串,比如:{“list”:[{“num”:“0”,“onlineShoppingCartId”:“20”}]}。
上面sessionManager使用的request方法请求参数类型是[String : Any],不支持发嵌套参数。强行发送请求的话示例如下:
请求类型:
application/json
请求参数:
list%5B%5D%5BonlineShoppingCartId%5D=20&list%5B%5D%5Bnum%5D=0
服务端接收参数抛出:
string(61) "list%5B%5D%5BonlineShoppingCartId%5D=20&list%5B%5D%5Bnum%5D=0"
更换请求方法,在方法中将请求参数转换成符合Encodable协议的参数。
/// Post请求 参数parameters必须符合Encodable协议
/// - Parameters:
/// - url: 请求链接
/// - params: 参数
/// - headers: header信息
/// - completed: 请求成功返回数据
/// - failed: 请求失败返回数据
fileprivate func EncodablePOST(url: String,
params: [String : Any]?,
headers: HTTPHeaders,
completed: @escaping CompletedBlock,
failed: @escaping FailedBlock) {
if let guardParams = params, guardParams.keys.contains("list") {
var parameters = PostEncodableParameters()
if let tempParams = guardParams["list"] as? [[String : String]] {
parameters.list = tempParams
} else {
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
return
}
AF.request(url, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, headers: headers).response { [weak self] response in
guard let data = response.data else {
DispatchQueue.main.async {
failed(.otherError, "服务器出错: \(response.error?.localizedDescription ?? "")")
}
return
}
guard let jsonData = try? JSON(data: data) else {
debugPrint("数据出错:\(url)", String(data: data, encoding: .utf8) ?? "")
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
return
}
let status = NetworkStatus(rawValue: jsonData["code"].stringValue)
switch status {
case .success:
completed(jsonData["data"])
case .tokenError:
self?.cancalAllRequest()
AppUserDefaults.shared.exitLoginAction(canToHome: true)
default:
DispatchQueue.main.async {
failed(.otherError, jsonData["msg"].stringValue)
}
}
}
} else {
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
}
}
extension JRNetworkManager {
struct PostEncodableParameters: Encodable {
var list: [[String : String]] = []
}
}
不支持Encodable协议时会报错
Protocol ‘Any’ as a type cannot conform to ‘Encodable’
- Only concrete types such as structs, enums and classes can conform to protocols
- Requirement from conditional conformance of ‘[String : Any]’ to ‘Encodable’
修复后的请求示例如下:
请求类型:
application/json
请求参数:
{"list":[{"num":"0","onlineShoppingCartId":"20"}]}
服务端接收参数抛出:
string(50) "{"list":[{"num":"0","onlineShoppingCartId":"20"}]}"
在body中传参
fileprivate func EncodablePOST(url: String,
params: [String : Any]?,
headers: HTTPHeaders,
completed: @escaping CompletedBlock,
failed: @escaping FailedBlock) {
var newHeaders = headers
newHeaders.updateValue("application/json", forKey: "Content-Type")
var jsonData: Data?
if let newParams = params {
if let json = ConversionHelper.ObjectToJSONString(newParams) {
jsonData = json.data(using: .utf8, allowLossyConversion: false)!
}
}
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = HTTPMethod.post.rawValue
request.allHTTPHeaderFields = newHeaders
//request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
Alamofire.request(request).responseJSON {
(response) in
if response.result.isSuccess {
let json = JSON(response.data!)
completed(json)
} else {
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
}
}
}
[以上|完结]
extension HomeVC {
func loadHomeRoute() {
JRNetworkManager.shared.request(HomeAPI.getHomeRoute(), completed: { [weak self] (result) in
guard let this = self else { return }
this.homeRouteModel = HomeRouteModel.parseObject(json: result)
DispatchQueue.main.async {
//this.collectionView.reloadData()
}
}) { (status, error) in
}
}
}
import Foundation
enum HomeAPI {
/// 首页
case getHomeRoute(Params = [:])
/// 商户小店
case getOfflineShopList(Params = [:])
}
extension HomeAPI : APITargetType {
var method: RequetMethod {
return .post
}
var baseURL: String {
return BASE_URL + "home/"
}
var path: String {
switch self {
case .getHomeRoute:
return "getHomeRoute"
case .getOfflineShopList:
return "getOfflineShopList"
}
}
var params: [String : Any] {
switch self {
case .getHomeRoute(let params):
fallthrough
case .getOfflineShopList(let params):
return params
}
}
}
import Foundation
import Alamofire
import SwiftyJSON
enum NetworkStatus : String {
case success = "00000"
case networkError = "A0001"
case otherError = "-1"
case tokenError = "-2"
}
class JRNetworkManager: NSObject {
private var sessionManager: Session?
static let shared = JRNetworkManager()
typealias CompletedBlock = ((JSON) -> Void)
typealias FailedBlock = ((NetworkStatus, String) -> Void)
private override init() {
super.init()
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 20
self.sessionManager = Session(configuration: configuration, delegate: SessionDelegate())
}
/// 判断是否联网
static func isNetworkConnect() -> Bool {
let networkStatue = NetworkReachabilityManager()
return networkStatue?.isReachable ?? true
}
/// 取消
func cancalAllRequest() {
if let session = sessionManager {
session.cancelAllRequests()
}
}
/**
Header
属性名 数据类型 是否必须 说明
token String 否 登录成功后的token,用户身份的唯一标识
appVersion String 是 APP版本号
osType String 是 操作系统类型1:iOS,2:Android,3:小程序,4:HarmonyOS,5:电脑端
osVersion String 否 操作系统版本
device String 否 设备型号,如:iphoneXS
lng String 否 经度
lat String 否 纬度
timestamp Long 否 请求时间戳(毫秒)
*/
func requestHeader() -> HTTPHeaders {
let token = AppUserDefaults.shared.token ?? ""
let infoDictionary = Bundle.main.infoDictionary!
let appVersion = infoDictionary["CFBundleShortVersionString"] as! String
let osVersion = UIDevice.current.systemVersion
let model = UIDevice.deviceModelReadable()
let longitude = AppUserDefaults.shared.longitude ?? ""
let latitude = AppUserDefaults.shared.latitude ?? ""
let timeInterval: TimeInterval = NSDate().timeIntervalSince1970
let millisecond = CLongLong(round(timeInterval*1000))
let time = String(millisecond)
let sign = ("JXF20210907" + time + token).md5()
let headers: HTTPHeaders = [
"Content-Type" : "application/x-www-form-urlencoded; charset=utf-8",
//"Content-Type" : "application/json",
"token" : token,
"appVersion" : appVersion,
"osType" : "1",
"osVersion" : osVersion,
"device" : model,
"lng" : longitude,
"lat" : latitude,
"timestamp" : time,
"sign" : sign,
]
return headers
}
/// 网络请求
/// - Parameters:
/// - target: 路由 遵守APITargetType枚举类型
/// - completed: 请求成功返回数据
/// - failed: 请求失败返回数据
func request(_ target: APITargetType, completed: @escaping CompletedBlock, failed: @escaping FailedBlock) {
let url = target.baseURL + target.path
switch target.method {
case .post:
if enterEncodablePOST(url: url) {
EncodablePOST(url: url, params: target.params, headers: requestHeader(), completed: completed, failed: failed)
} else {
POST(url: url, params: target.params, headers: requestHeader(), completed: completed, failed: failed)
}
default:
break
}
}
/// Post请求
/// - Parameters:
/// - url: 请求链接
/// - params: 参数
/// - headers: header信息
/// - completed: 请求成功返回数据
/// - failed: 请求失败返回数据
fileprivate func POST(url: String,
params: [String : Any]?,
headers: HTTPHeaders,
completed: @escaping CompletedBlock,
failed: @escaping FailedBlock) {
sessionManager?.request(url, method: .post, parameters: params, encoding: URLEncoding.default, headers: headers).validate().responseJSON { [weak self] (response) in
guard let data = response.data else {
DispatchQueue.main.async {
failed(.otherError, "服务器出错: \(response.error?.localizedDescription ?? "")")
}
return
}
guard let jsonData = try? JSON(data: data) else {
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
return
}
let status = NetworkStatus(rawValue: jsonData["code"].stringValue)
switch status {
case .success:
completed(jsonData["data"])
case .tokenError:
self?.cancalAllRequest()
AppUserDefaults.shared.exitLoginAction(canToHome: true)
default:
DispatchQueue.main.async {
failed(.otherError, jsonData["msg"].stringValue)
}
}
}
}
/// 判断是否进入EncodablePOST请求
fileprivate func enterEncodablePOST(url: String) -> Bool {
if url.isBlank {
return false
}
let keywords = ["test/test", "online_shop/setShoppingCart"]
for value in keywords {
if url.contains(value) {
return true
}
}
return false
}
/// Post请求 参数parameters必须符合Encodable协议
/// - Parameters:
/// - url: 请求链接
/// - params: 参数
/// - headers: header信息
/// - completed: 请求成功返回数据
/// - failed: 请求失败返回数据
fileprivate func EncodablePOST(url: String,
params: [String : Any]?,
headers: HTTPHeaders,
completed: @escaping CompletedBlock,
failed: @escaping FailedBlock) {
if !NetstatManager.shared.isNetworkConnect() {
debugPrint(GlobalConfig.noNetAlert)
DispatchQueue.main.async {
failed(.networkError, GlobalConfig.noNetAlert)
}
}
if let guardParams = params, guardParams.keys.contains("list") {
var parameters = PostEncodableParameters()
if let tempParams = guardParams["list"] as? [[String : String]] {
parameters.list = tempParams
} else {
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
return
}
AF.request(url, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, headers: headers).response { [weak self] response in
guard let data = response.data else {
DispatchQueue.main.async {
failed(.otherError, "服务器出错: \(response.error?.localizedDescription ?? "")")
}
return
}
guard let jsonData = try? JSON(data: data) else {
debugPrint("数据出错:\(url)", String(data: data, encoding: .utf8) ?? "")
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
return
}
let status = NetworkStatus(rawValue: jsonData["code"].stringValue)
switch status {
case .success:
completed(jsonData["data"])
case .tokenError:
self?.cancalAllRequest()
AppUserDefaults.shared.exitLoginAction(canToHome: true)
default:
DispatchQueue.main.async {
failed(.otherError, jsonData["msg"].stringValue)
}
}
}
} else {
DispatchQueue.main.async {
failed(.otherError, "数据出错")
}
}
}
}
extension JRNetworkManager {
struct PostEncodableParameters: Encodable {
var list: [[String : String]] = []
}
}
import Foundation
import Alamofire
/// 服务器地址
var ROOT_URL: String {
get {
return AppUserDefaults.shared.useDomain ?? ""
}
}
var BASE_URL: String {
get {
return ROOT_URL + "api/"
}
}
/// h5
var H5_URL: String {
get {
return "http://test.h5.test.com/"
}
}
/// 参数 [String: Any]
typealias Params = [String: Any]
/// 请求方式
enum RequetMethod : Int {
case get = 0
case post = 1
case bodyPost = 2
case uploadImage = 3
case uploadMp4 = 4
}
/// 网络请求抽象层
protocol APITargetType {
/// 请求方式
var method : RequetMethod { get }
/// 服务器地址
var baseURL : String { get }
/// 路由
var path: String { get }
/// 参数
var params: [String : Any] { get }
}
/// demo
enum ExampleAPI {
typealias Params = [String: Any]
case test(Params = [:])
}
extension ExampleAPI : APITargetType {
var method: RequetMethod {
return .post
}
var baseURL: String {
return BASE_URL
}
var path: String {
switch self {
case .test:
return "test"
}
}
var params: [String : Any] {
switch self {
case .test(let params):
var param = params
param["token"] = AppUserDefaults.shared.token ?? ""
return param
}
}
}