当前位置: 首页 > 工具软件 > Alamofire > 使用案例 >

[Swift]Alamofire使用嵌套参数进行网络请求遇到的问题

陶鸿畴
2023-12-01

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’

  1. Only concrete types such as structs, enums and classes can conform to protocols
  2. 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, "数据出错")
                }
            }
        }
    }

[以上|完结]

网络请求Demo备份:

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
        }
    }
    
}

 类似资料: